fix(subagent-codex): await managed tree exit
This commit is contained in:
@@ -24,54 +24,13 @@ import { CodexAppServerWire } from './wire.ts'
|
||||
/** Default POSIX grace between subprocess termination tiers. */
|
||||
export const DEFAULT_DISPOSE_GRACE_MS = 3_000
|
||||
|
||||
/** Largest delay Node schedules without collapsing it to one millisecond. */
|
||||
const MAX_TIMER_DELAY_MS = 2_147_483_647n
|
||||
|
||||
/**
|
||||
* Bound final exit observation at twice a positive finite grace without
|
||||
* narrowing the public config to Node's single-timer integer range.
|
||||
*/
|
||||
function doubledGraceWindow(graceMs: number): {
|
||||
readonly signal: AbortSignal
|
||||
readonly cancel: () => void
|
||||
} {
|
||||
const whole = Math.floor(graceMs)
|
||||
let remaining = BigInt(whole) * 2n
|
||||
+ BigInt(Math.ceil((graceMs - whole) * 2))
|
||||
const controller = new AbortController()
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
const arm = (): void => {
|
||||
const chunk = remaining > MAX_TIMER_DELAY_MS
|
||||
? MAX_TIMER_DELAY_MS
|
||||
: remaining
|
||||
remaining -= chunk
|
||||
timer = setTimeout(() => {
|
||||
timer = undefined
|
||||
if (remaining === 0n) {
|
||||
controller.abort()
|
||||
} else {
|
||||
arm()
|
||||
}
|
||||
}, Number(chunk))
|
||||
}
|
||||
arm()
|
||||
return {
|
||||
signal: controller.signal,
|
||||
cancel: () => {
|
||||
if (timer === undefined) return
|
||||
clearTimeout(timer)
|
||||
timer = undefined
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** Fully resolved inputs for one Codex app-server run. */
|
||||
export interface CodexRunSpec {
|
||||
/** Parent Session workspace, also supplied to `thread/start`. */
|
||||
readonly cwd: string
|
||||
/** Explicit deployment/test environment layered after the shared scrub. */
|
||||
readonly env: Record<string, string>
|
||||
/** Subprocess termination grace and final tree-exit bound. */
|
||||
/** Subprocess termination grace passed to the shared process-tree owner. */
|
||||
readonly disposeGraceMs: number
|
||||
/** Shared subprocess service spawn operation. */
|
||||
readonly spawn: (spec: SubprocessSpawnSpec) => SubprocessHandle
|
||||
@@ -111,12 +70,10 @@ export function textTask(prompt: readonly ContentBlock[]): string[] {
|
||||
* subprocess owner to prove it is gone.
|
||||
* @param wire - private app-server protocol connection.
|
||||
* @param child - shared-service handle that owns the process tree.
|
||||
* @param graceMs - termination grace used to bound final exit observation.
|
||||
*/
|
||||
export async function disposeCodexChild(
|
||||
wire: CodexAppServerWire,
|
||||
child: SubprocessHandle,
|
||||
graceMs: number,
|
||||
): Promise<void> {
|
||||
wire.close()
|
||||
if (child.pid <= 0) {
|
||||
@@ -129,14 +86,7 @@ export async function disposeCodexChild(
|
||||
// A concurrently closed stdin does not change tree ownership below.
|
||||
}
|
||||
child.terminate()
|
||||
const exitWindow = doubledGraceWindow(graceMs)
|
||||
try {
|
||||
if (!(await child.waitForExit(exitWindow.signal))) {
|
||||
throw new Error('subagent-codex: app-server process tree did not exit within its dispose window')
|
||||
}
|
||||
} finally {
|
||||
exitWindow.cancel()
|
||||
}
|
||||
await child.waitForExit()
|
||||
await child.done
|
||||
}
|
||||
|
||||
@@ -167,8 +117,7 @@ export async function startCodexRun(
|
||||
child.stdout as NonNullable<SubprocessHandle['stdout']>,
|
||||
child.stdin as NonNullable<SubprocessHandle['stdin']>,
|
||||
)
|
||||
const disposeProcess = (): Promise<void> =>
|
||||
disposeCodexChild(wire, child, spec.disposeGraceMs)
|
||||
const disposeProcess = (): Promise<void> => disposeCodexChild(wire, child)
|
||||
|
||||
const processFailure: Promise<never> = child.done.then(
|
||||
outcome => Promise.reject(new Error(
|
||||
@@ -205,7 +154,7 @@ export async function startCodexRun(
|
||||
)
|
||||
}
|
||||
if (runAbort.signal.aborted) {
|
||||
throw new Error('subagent-codex: request was aborted before app-server startup')
|
||||
throw new Error('subagent-codex: request was aborted before run publication')
|
||||
}
|
||||
throw thrown(error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user