(session: Session, timeoutMs: number)
| 44 | } |
| 45 | |
| 46 | async function waitForChildToStop(session: Session, timeoutMs: number): Promise<void> { |
| 47 | const child = session.process as ChildProcess | undefined; |
| 48 | if (!child) { |
| 49 | return; |
| 50 | } |
| 51 | |
| 52 | const alreadyEnded = session.ended || child.exitCode !== null; |
| 53 | const hasSignal = (child as unknown as { signalCode?: string | null }).signalCode != null; |
| 54 | if (alreadyEnded || hasSignal) { |
| 55 | session.ended = true; |
| 56 | return; |
| 57 | } |
| 58 | |
| 59 | const closePromise = new Promise<'closed'>((resolve) => { |
| 60 | let resolved = false; |
| 61 | const finish = (): void => { |
| 62 | if (!resolved) { |
| 63 | resolved = true; |
| 64 | session.ended = true; |
| 65 | resolve('closed'); |
| 66 | } |
| 67 | }; |
| 68 | |
| 69 | child.once('close', finish); |
| 70 | child.once('exit', finish); |
| 71 | }).catch(() => 'closed' as const); |
| 72 | |
| 73 | const outcome = await Promise.race([closePromise, createTimeoutPromise(timeoutMs)]); |
| 74 | if (outcome === 'timed_out') { |
| 75 | try { |
| 76 | child.kill('SIGKILL'); |
| 77 | } catch { |
| 78 | // ignore |
| 79 | } |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | type StopSessionSuccess = { sessionId: string; stdout: string; parsedPath?: string }; |
| 84 | type StopSessionError = { error: string }; |
no test coverage detected