diff --git a/services/worker/src/index.js b/services/worker/src/index.js index 7a4d18a..170879d 100644 --- a/services/worker/src/index.js +++ b/services/worker/src/index.js @@ -1,9 +1,9 @@ import 'dotenv/config'; import { Worker } from 'bullmq'; -import { proxyWorker } from './workers/proxy.js'; +import { proxyWorker, thumbnailQueue as proxyThumbnailQueue } from './workers/proxy.js'; import { thumbnailWorker } from './workers/thumbnail.js'; import { conformWorker } from './workers/conform.js'; -import { youtubeImportWorker } from './workers/youtube-import.js'; +import { youtubeImportWorker, proxyQueue as youtubeProxyQueue } from './workers/youtube-import.js'; import { trimWorker } from './workers/trimWorker.js'; import { startPromotionWorker } from './workers/promotion.js'; @@ -77,7 +77,9 @@ const workers = [ console.log(`Concurrency: proxy=${PROXY_CONCURRENCY} thumbnail=${THUMBNAIL_CONCURRENCY} conform=${CONFORM_CONCURRENCY} trim=${TRIM_CONCURRENCY} import=1`); -startPromotionWorker(); +// BUG FIX #4: startPromotionWorker() now returns a shutdown function that +// clears the poll intervals and closes the promotion proxyQueue singleton. +const stopPromotionWorker = startPromotionWorker(); console.log('Wild Dragon Worker Service started'); console.log(`Redis: ${redisOptions.host}:${redisOptions.port}`); @@ -86,6 +88,24 @@ console.log('Background scans: promotion (growing-files → S3)'); process.on('SIGTERM', async () => { console.log('SIGTERM received, shutting down...'); - await Promise.all(workers.map(w => w.close())); + + // BUG FIX #4 + #10: Close all BullMQ Workers AND all Queue client instances + // on SIGTERM. Workers process jobs; Queues dispatch them. Both hold open + // Redis connections that keep the event loop alive after workers are closed, + // causing the process to hang indefinitely unless process.exit() is called. + // Explicitly closing every Queue allows the event loop to drain naturally. + await Promise.all([ + // Close all Worker instances (stops accepting new jobs, waits for active) + ...workers.map(w => w.close()), + // BUG FIX #7: Close the Queue singletons from worker modules. + // proxyThumbnailQueue: thumbnailQueue in proxy.js (dispatches thumbnail jobs) + // youtubeProxyQueue: proxyQueue in youtube-import.js (dispatches proxy jobs) + proxyThumbnailQueue.close().catch(() => {}), + youtubeProxyQueue.close().catch(() => {}), + // BUG FIX #4: Stop the promotion worker intervals and close its proxyQueue + stopPromotionWorker(), + ]); + + console.log('All workers and queues closed'); process.exit(0); });