fix(runtime): close lifecycle teardown races

This commit is contained in:
Tianyi Cui
2026-07-29 02:40:02 +08:00
parent c17727d43d
commit 32bce339da
20 changed files with 233 additions and 36 deletions

View File

@@ -101,15 +101,30 @@ export class LocalPtyBackend implements PtyBackend {
ensureSandboxModeFence(this.ctx, spec.owner)
const argv = spawnArgv(this.ctx, this.config, spec)
if (argv[0] === undefined) throw new Error('pty-local: sandbox returned empty argv')
const terminal = await this.spawnTerminal({
argv,
cwd: spec.cwd ?? this.ctx.sandboxPolicy.workspaceRoot,
env: childEnvironment(spec),
rows: this.config.rows,
cols: this.config.cols,
graceMs: this.config.disposeGraceMs,
signal: spec.signal,
})
let terminalSignal: AbortSignal | undefined
let detachSetupSignal: (() => void) | undefined
if (spec.signal !== undefined) {
const source = spec.signal
const controller = new AbortController()
const onAbort = (): void => { controller.abort(source.reason) }
source.addEventListener('abort', onAbort, { once: true })
detachSetupSignal = () => { source.removeEventListener('abort', onAbort) }
terminalSignal = controller.signal
}
let terminal: SubprocessTerminalHandle
try {
terminal = await this.spawnTerminal({
argv,
cwd: spec.cwd ?? this.ctx.sandboxPolicy.workspaceRoot,
env: childEnvironment(spec),
rows: this.config.rows,
cols: this.config.cols,
graceMs: this.config.disposeGraceMs,
signal: terminalSignal,
})
} finally {
detachSetupSignal?.()
}
const session = this.createSession(terminal, this.config)
try {
await session.initialize(spec.signal)

View File

@@ -473,17 +473,23 @@ export class LocalPtySession implements PtyBackendSession {
this.active = undefined
}
private failActive(error: unknown): void {
private failActive(error: unknown, retainOwnership = false): void {
const operation = this.active
if (operation === undefined) return
this.clearActive()
if (retainOwnership) {
this.stopPolling()
this.activeAbort?.()
this.activeAbort = undefined
} else {
this.clearActive()
}
operation.fail(error)
}
private interrupt(operation: LocalSendOperation): void {
if (this.active !== operation) return
void this.terminal.signalForeground('SIGINT').catch((error: unknown) => {
if (this.active === operation) this.failActive(error)
if (this.active === operation) this.failActive(error, this.writing === operation)
})
}