fix(embedded-mpv): close helper stdin on dispose + lifecycle logging

Live testing surfaced a stray idle helper that survived a session switch;
until the root cause is pinned down, dispose now also closes the child's
stdin (the helper exits on EOF) as a second kill path besides quit ->
SIGTERM -> SIGKILL, and spawn/dispose/exit are logged with the session id
so leaks are attributable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 committed 2026-07-15 18:20:45 +02:00
1 parent f4da4d6074
commit e36cc44d44
1 file changed
+14
@@ -120,6 +120,9 @@ export class EmbeddedMpvFrameCopyAdapter implements NativeEmbeddedMpvAddon {
{ stdio: ['pipe', 'pipe', 'pipe'] }
);
console.log(
`[embedded-mpv-fc][${sessionId}] spawn ${width}x${height} (pid pending)`
);
const session: FrameCopyRuntimeSession = {
id: sessionId,
child,
@@ -144,6 +147,9 @@ export class EmbeddedMpvFrameCopyAdapter implements NativeEmbeddedMpvAddon {
session.snapshot.error = `Helper process failed: ${error.message}`;
});
child.on('exit', (code, signal) => {
console.log(
`[embedded-mpv-fc][${sessionId}] exit code=${code} signal=${signal} disposed=${session.disposed}`
);
session.killTimers.forEach((timer) => clearTimeout(timer));
session.killTimers = [];
if (session.disposed) {
@@ -261,8 +267,16 @@ export class EmbeddedMpvFrameCopyAdapter implements NativeEmbeddedMpvAddon {
return;
}
session.disposed = true;
console.log(`[embedded-mpv-fc][${sessionId}] dispose`);
this.send(sessionId, 'quit');
const child = session.child;
// Belt and braces: the helper also exits on stdin EOF, so closing
// the pipe covers a helper that missed the quit line.
try {
child.stdin.end();
} catch {
// stdin may already be destroyed with the process
}
session.killTimers.push(
setTimeout(() => {
if (child.exitCode === null) child.kill('SIGTERM');