@@ -217,17 +217,34 @@ function waitChildClose(child: ChildProcess): Promise<void> {
217217
218218const SHELL_GUARD_DISPOSE_REAP_MS = 2_000 ;
219219
220- async function reapLiveChildren ( liveChildren : Set < ChildProcess > ) : Promise < void > {
220+ function childStillLive ( child : ChildProcess ) : boolean {
221+ return child . exitCode === null && child . signalCode === null ;
222+ }
223+
224+ // Abort SIGKILLs the process group immediately (runGuardedShell onAbort). This
225+ // window is only a backstop for children still tracked at dispose. Leftovers
226+ // after it must fail teardown; do not stretch the process-exit 2s deadline.
227+ export async function reapLiveChildren ( liveChildren : Set < ChildProcess > ) : Promise < void > {
221228 const remaining = [ ...liveChildren ] ;
222229 for ( const child of remaining ) killProcessTree ( child ) ;
223230 if ( remaining . length === 0 ) return ;
224- await Promise . race ( [
225- Promise . all ( remaining . map ( waitChildClose ) ) ,
226- new Promise < void > ( ( resolve ) => {
227- const timer = setTimeout ( resolve , SHELL_GUARD_DISPOSE_REAP_MS ) ;
228- if ( typeof timer . unref === "function" ) timer . unref ( ) ;
229- } ) ,
230- ] ) ;
231+ const closed = Promise . all ( remaining . map ( waitChildClose ) ) ;
232+ let timer : ReturnType < typeof setTimeout > | undefined ;
233+ const timedOut = new Promise < "timeout" > ( ( resolve ) => {
234+ timer = setTimeout ( ( ) => resolve ( "timeout" ) , SHELL_GUARD_DISPOSE_REAP_MS ) ;
235+ } ) ;
236+ try {
237+ const winner = await Promise . race ( [ closed . then ( ( ) => "closed" as const ) , timedOut ] ) ;
238+ if ( winner === "closed" ) return ;
239+ const stillLive = remaining . filter ( childStillLive ) ;
240+ if ( stillLive . length > 0 ) {
241+ throw new Error (
242+ `${ stillLive . length } shell child process${ stillLive . length === 1 ? "" : "es" } still live after ${ SHELL_GUARD_DISPOSE_REAP_MS } ms reap` ,
243+ ) ;
244+ }
245+ } finally {
246+ if ( timer !== undefined ) clearTimeout ( timer ) ;
247+ }
231248}
232249export async function runGuardedShell (
233250 args : RunShellArgs ,
0 commit comments