Files
MultiRoombaRover/server/src/services/replayEngineV2/workerManager.js
T
2026-04-29 13:21:44 -04:00

75 lines
2.7 KiB
JavaScript

// Replay Worker Manager
// Purpose: Starts/stops ffmpeg segment workers and keeps active worker set aligned with desired sources.
// Scope: Owns worker process lifecycle and restart behavior for replay segment capture.
const { spawn } = require('child_process');
const fsp = require('fs/promises');
const logger = require('../../globals/logger').child('replayEngineV2');
const { FFMPEG_BIN } = require('./constants');
const { workers, pendingWorkerStarts } = require('./state');
const { sourceKey, sourceDirForKey, listDesiredSources, buildWorkerArgs } = require('./sources');
async function ensureDir(dir) {
await fsp.mkdir(dir, { recursive: true });
}
function createWorkerManager({ getActiveSegmentRoot }) {
function startWorker(source) {
const key = sourceKey(source);
if (workers.has(key) || pendingWorkerStarts.has(key)) return;
pendingWorkerStarts.add(key);
const args = buildWorkerArgs(getActiveSegmentRoot(), source);
const dir = sourceDirForKey(getActiveSegmentRoot(), key);
ensureDir(dir)
.then(() => {
if (workers.has(key)) {
pendingWorkerStarts.delete(key);
return;
}
const proc = spawn(FFMPEG_BIN, args, { stdio: ['ignore', 'ignore', 'pipe'] });
workers.set(key, { key, source, proc });
pendingWorkerStarts.delete(key);
proc.stderr.on('data', (chunk) => {
const text = String(chunk || '').trim();
if (!text) return;
logger.warn('worker stderr', { key, text: text.slice(0, 500) });
});
proc.on('exit', (code, signal) => {
const current = workers.get(key);
if (current?.proc === proc) workers.delete(key);
logger.warn('worker exited', { key, code, signal });
setTimeout(() => {
const desired = listDesiredSources().find((entry) => sourceKey(entry) === key);
if (desired && !workers.has(key)) startWorker(desired);
}, 1500);
});
})
.catch((err) => {
pendingWorkerStarts.delete(key);
logger.warn('failed to start worker', { key, error: err.message });
});
}
function stopWorker(key) {
const worker = workers.get(key);
if (!worker) return;
try { worker.proc.kill('SIGTERM'); } catch {}
workers.delete(key);
}
async function syncWorkers() {
const desired = listDesiredSources();
const desiredKeys = new Set(desired.map(sourceKey));
for (const source of desired) {
const key = sourceKey(source);
if (!workers.has(key)) startWorker(source);
}
for (const key of Array.from(workers.keys())) {
if (!desiredKeys.has(key)) stopWorker(key);
}
}
return { startWorker, stopWorker, syncWorkers };
}
module.exports = { createWorkerManager };