From d562c0588214885a6efdde15d0a8f4f624edcb04 Mon Sep 17 00:00:00 2001 From: ayrisdev Date: Sun, 30 Aug 2026 18:54:47 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20yeniden=20ba=C5=9Flatma=20sonras=C4=B1?= =?UTF-8?q?=20a=C3=A7=C4=B1k=20kalan=20session'lar=C4=B1=20temizle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bir redeploy, devam eden capture'ı temizlik kodu hiç çalışmadan öldürüyordu — StreamSession.endedAt sonsuza dek null kalıyordu. Bu sadece panelde yanlış "kayıtta" göstermekle kalmıyor, startSession() açık bir session gördüğünde o kanal için yeni session başlatmayı tamamen atlıyor — yani o kanal bir daha asla kayda alınamıyordu. api-daemon başlarken artık tüm açık session'ları kapatıyor (taze bir process başlangıcı = gerçekte devam eden capture yok demektir). BullMQ lockDuration da 24 saatten 10 dakikaya indirildi — worker canlıyken kilit otomatik yenileniyor, düşük değer sadece ölü bir worker'ın job'ının ne kadar hızlı kurtarılacağını etkiliyor. Co-Authored-By: Claude Sonnet 5 --- apps/api-daemon/src/capture/streamIngest.ts | 10 +++++++--- apps/api-daemon/src/index.ts | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/apps/api-daemon/src/capture/streamIngest.ts b/apps/api-daemon/src/capture/streamIngest.ts index 4db287e..3fcb75e 100644 --- a/apps/api-daemon/src/capture/streamIngest.ts +++ b/apps/api-daemon/src/capture/streamIngest.ts @@ -181,8 +181,12 @@ export function startStreamIngestWorker(): Worker { // count; the real ceiling is the server's CPU/bandwidth for running that // many yt-dlp+ffmpeg pairs at once, not this number. concurrency: env.maxConcurrentCaptures, - // Captures run for the lifetime of the stream (can be hours) — extend the - // default lock well beyond BullMQ's stalled-job assumptions for an MVP. - lockDuration: 24 * 60 * 60 * 1000, + // BullMQ renews this lock automatically while a worker is alive, so a + // low value doesn't cap how long a capture can run — it only controls + // how fast a *dead* worker's job is detected as stalled and recovered. + // (An earlier 24h value meant a killed process's job wouldn't be + // recovered for up to 24h; closeOrphanedSessions() in index.ts is what + // actually unblocks that channel's StreamSession in the meantime.) + lockDuration: 10 * 60 * 1000, }); } diff --git a/apps/api-daemon/src/index.ts b/apps/api-daemon/src/index.ts index de28a1f..1570139 100644 --- a/apps/api-daemon/src/index.ts +++ b/apps/api-daemon/src/index.ts @@ -30,7 +30,28 @@ async function forceLiveBypass(youtubeUrl: string) { }); } +/** + * A container restart (e.g. a redeploy) kills any in-progress capture + * without running its cleanup code, leaving that StreamSession's endedAt + * stuck at null forever. Besides showing "still recording" in the panel, + * startSession() treats any open session as a reason to skip starting a new + * one for that channel — so an orphaned session permanently blocks that + * channel from ever being recorded again until this runs. Since a fresh + * process start means no capture can actually still be in progress, every + * open session at boot is stale by definition and gets closed out. + */ +async function closeOrphanedSessions() { + const { count } = await prisma.streamSession.updateMany({ + where: { endedAt: null }, + data: { endedAt: new Date() }, + }); + if (count > 0) { + console.log(`[index] closed ${count} orphaned session(s) left open by a previous process`); + } +} + async function main() { + await closeOrphanedSessions(); startServer(); startStreamIngestWorker();