@@ -56,33 +56,19 @@ export type DaemonProcessIdentity = {
5656} ;
5757
5858export type DaemonExitWait = {
59- /** The pid no longer belongs to the identity: it exited , or the host recycled it. */
59+ /** The pid was released , or the host handed it to a different process . */
6060 exited : boolean ;
6161 elapsedMs : number ;
6262} ;
6363
64- /**
65- * Poll for {@link waitForDaemonExit}. {@link classifyDaemonPid} reads `ps` only
66- * once the cheap liveness check says the pid is still taken, so a daemon that has
67- * already gone costs no subprocess at all.
68- */
6964const DAEMON_EXIT_POLL_MS = 100 ;
7065
71- /**
72- * What a pid is doing relative to the identity that claimed it. `exiting` is the
73- * state a bare liveness read cannot express: `kill(pid, 0)` still succeeds for a
74- * process that has died but has not been reaped yet, while `ps` has already
75- * dropped its row — a pid on its way out, not a pid handed to somebody else.
76- */
7766type DaemonPidState = 'ours' | 'exiting' | 'released' | 'recycled' ;
7867
7968function classifyDaemonPid ( identity : DaemonProcessIdentity ) : DaemonPidState {
8069 if ( ! isProcessAlive ( identity . pid ) ) return 'released' ;
81- // State and start time in one `ps` read. A process that has died but has not
82- // been reaped answers kill(pid, 0) AND still reports its original start time,
83- // so the state is the only field that separates it from one still running —
84- // and its command reads `<defunct>`, which would otherwise look like a pid
85- // handed to a different program.
70+ // A terminated pid awaiting reap answers kill(pid, 0), keeps its start time, and
71+ // reports its command as `<defunct>`; only the process state distinguishes it.
8672 const observed = readHostProcessIdentityObservations ( [ identity . pid ] ) . get ( identity . pid ) ;
8773 if ( ! observed || observed . state . startsWith ( 'Z' ) ) return 'exiting' ;
8874 if ( observed . startTime !== identity . startTime ) return 'recycled' ;
@@ -92,13 +78,8 @@ function classifyDaemonPid(identity: DaemonProcessIdentity): DaemonPidState {
9278}
9379
9480/**
95- * Waits for a daemon identity to leave the host. Two endings count as exited: the
96- * pid was released, or the host handed it to someone else. Recycling is the one a
97- * bare liveness wait gets wrong — it reports a stranger's pid as "still running",
98- * which is what lets a grace wait escalate a SIGKILL onto an unrelated process —
99- * so escalating callers must branch on this result, never on bare liveness. A pid
100- * still being torn down is neither ending, so the wait keeps polling and callers
101- * retain the old guarantee that the number is free before they act on it.
81+ * Resolves once `identity` has left the host — released or recycled. A pid still
82+ * being torn down is neither, so the wait continues until the number is free.
10283 */
10384export async function waitForDaemonExit (
10485 identity : DaemonProcessIdentity ,
@@ -119,12 +100,6 @@ export async function waitForDaemonExit(
119100 return { exited, elapsedMs : Date . now ( ) - startedAt } ;
120101}
121102
122- /**
123- * The only way this module signals a daemon: identity is re-read immediately
124- * before the write, so a pid recycled since the last observation cannot be
125- * signaled at all. Escalation safety is then a property of the call, not a rule
126- * each call site has to remember.
127- */
128103function signalDaemonIdentity ( identity : DaemonProcessIdentity , signal : NodeJS . Signals ) : boolean {
129104 if ( ! isAgentDeviceDaemonProcess ( identity . pid , identity . startTime ) ) return false ;
130105 return trySignalProcess ( identity . pid , signal ) ;
0 commit comments