fix(node-agent): await stopDecklinkBridge and clean up stale occurrences
This commit is contained in:
parent
d1b40f5303
commit
315b31a68b
1 changed files with 98 additions and 5 deletions
|
|
@ -77,7 +77,7 @@ const DC_BOARD = process.env.DELTACAST_BOARD || '0';
|
|||
const FC_URL = process.env.FC_URL || 'http://framecache:7435';
|
||||
// Node identity for framecache slot IDs (e.g. "decklink-zampp3-0").
|
||||
// Set NODE_NAME in .env.worker so slot IDs are stable across restarts.
|
||||
const FC_NODE_ID = process.env.NODE_NAME || os.hostname();
|
||||
const FC_NODE_ID = process.env.NODE_NAME || process.env.HOSTNAME || 'local';
|
||||
|
||||
let _dcBridge = null; // ChildProcess | null
|
||||
let _dcSidecarCount = 0; // active deltacast sidecars on this node
|
||||
|
|
@ -205,6 +205,9 @@ async function startDecklinkBridge(deviceIndices) {
|
|||
if (await _dlBridgeRunning()) return;
|
||||
|
||||
const devCsv = Array.isArray(deviceIndices) ? deviceIndices.join(',') : String(deviceIndices || '0');
|
||||
const DL_IMAGE = 'wild-dragon-capture:latest';
|
||||
const DL_BIN = '/usr/local/bin/decklink-bridge';
|
||||
|
||||
// Pass correct IP to containerized bridge. Default falls back to framecache:7435.
|
||||
const _fcUrl = process.env.FRAMECACHE_IP ? `http://${process.env.FRAMECACHE_IP}:7435` : FC_URL;
|
||||
|
||||
|
|
@ -213,12 +216,102 @@ async function startDecklinkBridge(deviceIndices) {
|
|||
'--fc-url', _fcUrl,
|
||||
'--audio-pipe-dir', DL_AUDIO_DIR,
|
||||
];
|
||||
|
||||
|
||||
console.log(`[dl-bridge] spawning containerized bridge for devices: ${devCsv}`);
|
||||
|
||||
const spec = {
|
||||
Image: DL_IMAGE,
|
||||
Entrypoint: [DL_BIN],
|
||||
Cmd: bridgeArgs,
|
||||
Env: [`NODE_ID=${FC_NODE_ID}`, `FC_URL=${_fcUrl}`],
|
||||
HostConfig: {
|
||||
NetworkMode: 'host',
|
||||
Privileged: true,
|
||||
Binds: ['/dev:/dev', '/dev/shm:/dev/shm'],
|
||||
RestartPolicy: { Name: 'unless-stopped' },
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const createRes = await dockerApi('POST', '/containers/create?name=decklink-bridge', spec);
|
||||
if (createRes.status !== 201 && createRes.status !== 409) {
|
||||
console.error('[dl-bridge] create failed:', createRes.data);
|
||||
return;
|
||||
}
|
||||
|
||||
const containerId = createRes.status === 409 ? 'decklink-bridge' : createRes.data.Id;
|
||||
const startRes = await dockerApi('POST', `/containers/${containerId}/start`);
|
||||
if (startRes.status !== 204 && startRes.status !== 304) {
|
||||
console.error('[dl-bridge] start failed:', startRes.data);
|
||||
return;
|
||||
}
|
||||
|
||||
_dlBridgeId = containerId;
|
||||
_attachDlBridgeLogs(containerId);
|
||||
console.log(`[dl-bridge] running in container ${containerId}`);
|
||||
} catch (err) {
|
||||
console.error(`[dl-bridge] spawn error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function stopDecklinkBridge() {
|
||||
if (!_dlBridgeId) return;
|
||||
console.log('[dl-bridge] stopping container');
|
||||
try {
|
||||
await dockerApi('POST', `/containers/${_dlBridgeId}/stop?t=5`);
|
||||
await dockerApi('DELETE', `/containers/${_dlBridgeId}?force=true`);
|
||||
} catch (err) {
|
||||
console.error(`[dl-bridge] stop error: ${err.message}`);
|
||||
}
|
||||
_dlBridgeId = null;
|
||||
}
|
||||
|
||||
function _dcBridgeRunning() {
|
||||
return _dcBridge !== null && _dcBridge.exitCode === null && _dcBridge.signalCode === null;
|
||||
}
|
||||
|
||||
// Check /proc on Linux to see if a deltacast-bridge process is alive.
|
||||
// Used by startDeltacastBridge() to detect a bridge started outside node-agent
|
||||
// (e.g. manually with sudo, or from a prior node-agent process).
|
||||
function _dcBridgeProcessAlive() {
|
||||
try {
|
||||
for (const pid of fs.readdirSync('/proc')) {
|
||||
if (!/^\d+$/.test(pid)) continue;
|
||||
try {
|
||||
// cmdline is NUL-delimited; read as binary-friendly string.
|
||||
const cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, 'latin1');
|
||||
if (cmdline.includes('deltacast-bridge')) return true;
|
||||
} catch (_) { /* process may have exited mid-scan */ }
|
||||
}
|
||||
} catch (_) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
function startDeltacastBridge() {
|
||||
if (_dcBridgeRunning()) return; // already up (we spawned it)
|
||||
|
||||
try { fs.mkdirSync(DC_PIPE_DIR, { recursive: true }); } catch (_) {}
|
||||
|
||||
// FIFOs may exist from a previous run. Only skip the spawn if a
|
||||
// deltacast-bridge process is actually alive on the host — stale FIFOs with
|
||||
// no live writer cause ffmpeg to block on open() indefinitely (no audio/video).
|
||||
const _v0 = DC_PIPE_DIR + '/video-0.fifo';
|
||||
if (fs.existsSync(_v0)) {
|
||||
if (_dcBridgeProcessAlive()) {
|
||||
console.log('[dc-bridge] FIFOs exist and bridge process alive — skipping spawn');
|
||||
return;
|
||||
}
|
||||
console.log('[dc-bridge] FIFOs exist but bridge is NOT running — spawning fresh bridge');
|
||||
// Stale FIFOs are harmless: the bridge recreates them (mkfifo ignores EEXIST).
|
||||
}
|
||||
|
||||
const args = [
|
||||
'--device', DC_BOARD,
|
||||
'--ports', DC_PORTS_CSV,
|
||||
'--video-pipe-dir', DC_PIPE_DIR,
|
||||
'--audio-pipe-dir', DC_PIPE_DIR,
|
||||
'--fc-url', FC_URL,
|
||||
];
|
||||
console.log(`[dc-bridge] launching: ${DC_BRIDGE_BIN} ${args.join(' ')}`);
|
||||
|
||||
const proc = spawn(DC_BRIDGE_BIN, args, {
|
||||
|
|
@ -529,7 +622,7 @@ async function handleSidecarStart(body, res) {
|
|||
if (_dcSidecarCount <= 0) { _dcSidecarCount = 0; stopDeltacastBridge(); }
|
||||
} else if (sourceType === 'sdi' || sourceType === 'blackmagic') {
|
||||
_dlSidecarCount--;
|
||||
if (_dlSidecarCount <= 0) { _dlSidecarCount = 0; stopDecklinkBridge(); }
|
||||
if (_dlSidecarCount <= 0) { _dlSidecarCount = 0; await stopDecklinkBridge(); }
|
||||
} else if (sourceType === 'srt' || sourceType === 'rtmp') {
|
||||
// net_ingest may be keyed by the temp id (create not yet succeeded) or
|
||||
// the real containerId (remapped). Stop whichever exists.
|
||||
|
|
@ -687,7 +780,7 @@ async function handleSidecarStandby(body, res) {
|
|||
if (_dcSidecarCount <= 0) { _dcSidecarCount = 0; stopDeltacastBridge(); }
|
||||
} else if (sourceType === 'sdi' || sourceType === 'blackmagic') {
|
||||
_dlSidecarCount--;
|
||||
if (_dlSidecarCount <= 0) { _dlSidecarCount = 0; stopDecklinkBridge(); }
|
||||
if (_dlSidecarCount <= 0) { _dlSidecarCount = 0; await stopDecklinkBridge(); }
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -746,7 +839,7 @@ async function handleSidecarStop(containerId, res) {
|
|||
_dlSidecarCount--;
|
||||
if (_dlSidecarCount <= 0) {
|
||||
_dlSidecarCount = 0;
|
||||
stopDecklinkBridge();
|
||||
await stopDecklinkBridge();
|
||||
}
|
||||
} else if (_srcType === 'srt' || _srcType === 'rtmp') {
|
||||
stopNetIngest(containerId);
|
||||
|
|
|
|||
Loading…
Reference in a new issue