Keep one long-lived yt-dlp worker process instead of spawning per call
This commit is contained in:
@@ -50,6 +50,7 @@ import { createPartyHub } from './party.js';
|
||||
import { registerUploadRoutes } from './uploads.js';
|
||||
import * as innertube from './innertube.js';
|
||||
import QRCode from 'qrcode';
|
||||
import { createYtdlpPool } from './ytdlp-pool.js';
|
||||
import { dirname, join as pathJoin } from 'node:path';
|
||||
|
||||
// A media proxy must not die because one client's stream hit an edge case
|
||||
@@ -142,7 +143,7 @@ const CHANNEL_LIMIT = 60;
|
||||
// concurrent request — including in-flight /api/download proxy streams, which
|
||||
// Bun then kills at its idle timeout ("fetch failed" mid-download on clients).
|
||||
// Rejects on non-zero exit.
|
||||
function runYtdlp(args, { signal } = {}) {
|
||||
function runYtdlpSpawn(args, { signal } = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(YTDLP, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
const t0 = Date.now();
|
||||
@@ -170,6 +171,29 @@ function runYtdlp(args, { signal } = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
// Read-only calls (-J, --dump-json) go to long-lived workers that import
|
||||
// yt_dlp once (~1 s saved per call). Downloads keep spawning. Pool trouble
|
||||
// (not a yt-dlp error) falls back to a spawn. YTDLP_WORKER=0 disables it.
|
||||
const ytdlpPool = process.env.YTDLP_WORKER === '0' ? null : createYtdlpPool({
|
||||
ytdlpPath: Bun.which(YTDLP) || YTDLP,
|
||||
size: Math.max(1, Number(process.env.YTDLP_WORKERS) || 2),
|
||||
});
|
||||
function runYtdlp(args, opts = {}) {
|
||||
if (!opts.pooled || !ytdlpPool || opts.signal) return runYtdlpSpawn(args, opts);
|
||||
const t0 = Date.now();
|
||||
return ytdlpPool.run(args).then(
|
||||
(out) => { console.log(`[ytdlp] pooled ${Date.now() - t0}ms ok`); return out; },
|
||||
(err) => {
|
||||
if (err.poolInfra) return runYtdlpSpawn(args, opts);
|
||||
// A bot check can stick to a long-lived process: replace the workers and
|
||||
// answer this call the old way, from a fresh process.
|
||||
if (BOT_CHECK_RE.test(err.message)) { ytdlpPool.recycle(); return runYtdlpSpawn(args, opts); }
|
||||
console.log(`[ytdlp] pooled ${Date.now() - t0}ms fail`);
|
||||
throw err;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// YouTube intermittently answers the default (web) innertube client with
|
||||
// "Sign in to confirm you're not a bot" — a per-IP rate signal, not a
|
||||
// per-video one, so the SAME video that just saved fine fails minutes later
|
||||
@@ -396,7 +420,7 @@ app.get('/api/search', async (c) => {
|
||||
`ytsearch${SEARCH_LIMIT}:${q}`,
|
||||
'--dump-json', '--flat-playlist',
|
||||
'--no-warnings', '--ignore-errors',
|
||||
]);
|
||||
], { pooled: true });
|
||||
yt = parseCards(out);
|
||||
}
|
||||
const results = [...mine, ...yt];
|
||||
@@ -421,7 +445,7 @@ app.get('/api/channel', async (c) => {
|
||||
'--dump-json', '--flat-playlist',
|
||||
'--no-warnings', '--ignore-errors',
|
||||
'--playlist-end', String(CHANNEL_LIMIT),
|
||||
]);
|
||||
], { pooled: true });
|
||||
const results = parseCards(out);
|
||||
// Extract channel name + URL from the first record
|
||||
const first = results[0];
|
||||
@@ -476,7 +500,7 @@ async function resolveStreamsUncached(videoId) {
|
||||
const cached = streamCache.get(videoId);
|
||||
if (cached && now < cached.expiresAt) return cached;
|
||||
|
||||
const out = await runYtdlpResilient(['-J', '--no-warnings', `https://www.youtube.com/watch?v=${videoId}`]);
|
||||
const out = await runYtdlpResilient(['-J', '--no-warnings', `https://www.youtube.com/watch?v=${videoId}`], { pooled: true });
|
||||
const info = JSON.parse(out);
|
||||
const raw = Array.isArray(info.formats) ? info.formats : [];
|
||||
const formats = [];
|
||||
@@ -1760,7 +1784,7 @@ app.get('/api/playlist/expand', async (c) => {
|
||||
'--dump-json', '--flat-playlist',
|
||||
'--no-warnings', '--ignore-errors',
|
||||
'--playlist-end', '201',
|
||||
]);
|
||||
], { pooled: true });
|
||||
const parsed = parseCards(out);
|
||||
let title = '';
|
||||
for (const line of out.split('\n')) {
|
||||
|
||||
Reference in New Issue
Block a user