a job's controls can no longer freeze the platform
The server went down. It had not: it was STOPPED — eleven threads in state T, port 9000 still listening with connections queueing behind a process that would never accept them. Indistinguishable from a crash unless you look, which is why it took a terminal to see. `kill -CONT` brought it back with nothing lost. I did it, with one missing line. `restartBackgroundTask` opened the job's output file to hand the child a descriptor and never closed it, so the PLATFORM became a holder of that file. `pidsHoldingFile` asks `fuser` who holds it — that is how Stop finds a job with no recorded pid — so the platform was in the answer. The next Pause sent SIGSTOP to it. Every part worked as designed. Pause froze exactly what was holding the file, and one of those things was the thing doing the freezing. Two fixes, and the second matters more than the first: **Close the descriptor.** The child gets its own duplicate at spawn, so closing costs it nothing. That removes this instance. **Never signal our own pids, whatever `fuser` says.** `pidsHoldingFile` now filters `process.pid` and `process.ppid`. The leak is fixed; this is what makes the next one survivable. Nothing that manages a job should be able to name the process doing the managing — a rule worth having independently of the bug that suggested it. Verified: after a restart this process holds the output file zero times, `fuser` returns only the spawned shell and its child, and our own pid is filtered even when present. tsgo clean, frontend builds, 901 pass / 7 fail unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -744,10 +744,17 @@ export async function pidsHoldingFile(who: ChatIdentity, path: string): Promise<
|
||||
const proc = Bun.spawn(argv, { stdout: 'pipe', stderr: 'ignore' });
|
||||
const out = await new Response(proc.stdout).text();
|
||||
await proc.exited;
|
||||
// The platform's OWN pids are removed, always.
|
||||
//
|
||||
// Not defensive programming for its own sake: a leaked descriptor made the server a holder of a job's
|
||||
// output file, and the next Pause `SIGSTOP`ped the whole platform through this list. The leak is fixed,
|
||||
// and this is the guard that means the next one cannot do the same thing. Nothing that manages a job
|
||||
// should ever be able to name the process doing the managing.
|
||||
const self = new Set([process.pid, process.ppid]);
|
||||
return out
|
||||
.split(/\s+/)
|
||||
.map((token) => Number(token.replace(/\D/g, '')))
|
||||
.filter((n) => Number.isInteger(n) && n > 1);
|
||||
.filter((n) => Number.isInteger(n) && n > 1 && !self.has(n));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user