From c3d2f39b823da63cfcceb979cc07c4bbc9aa6c35 Mon Sep 17 00:00:00 2001 From: TorMatzAndren Date: Sat, 16 May 2026 11:42:16 +0200 Subject: [PATCH] Harden yt-dlp process lifecycle --- src/backend/src/shared/youtube.service.ts | 43 ++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/backend/src/shared/youtube.service.ts b/src/backend/src/shared/youtube.service.ts index 9fa8b95..30b0ca3 100644 --- a/src/backend/src/shared/youtube.service.ts +++ b/src/backend/src/shared/youtube.service.ts @@ -407,6 +407,37 @@ export class YoutubeService { stdio: ['ignore', 'pipe', 'pipe'], }); + const timeoutMs = 10 * 60 * 1000; + let settled = false; + + const settleResolve = (value: string) => { + if (settled) { + return; + } + + settled = true; + clearTimeout(timeout); + resolvePromise(value); + }; + + const settleReject = (error: Error) => { + if (settled) { + return; + } + + settled = true; + clearTimeout(timeout); + rejectPromise(error); + }; + + const timeout = setTimeout(() => { + child.kill('SIGKILL'); + + settleReject( + new Error(`yt-dlp exceeded maximum runtime of ${timeoutMs}ms`), + ); + }, timeoutMs); + let stdout = ''; let stderr = ''; @@ -419,16 +450,20 @@ export class YoutubeService { }); child.on('error', (error) => { - rejectPromise(new Error(`Failed to start yt-dlp: ${error.message}`)); + settleReject(new Error(`Failed to start yt-dlp: ${error.message}`)); }); child.on('close', (code) => { - if (code === 0) { - resolvePromise(stdout); + if (settled) { return; } - rejectPromise( + if (code === 0) { + settleResolve(stdout); + return; + } + + settleReject( new Error( `yt-dlp exited with code ${code}: ${this.classifyYtDlpError(stderr || stdout)}`, ),