fix(e2b): preserve signals after output drain

This commit is contained in:
Tianyi Cui
2026-07-29 07:07:31 +08:00
parent ec0310ca1b
commit e9bef2967d
2 changed files with 24 additions and 2 deletions

View File

@@ -512,7 +512,9 @@ export class E2BSubprocessHandle implements SubprocessHandle {
this.stdoutReader?.invalidateSpill()
this.stderrReader?.invalidateSpill()
await handle.disconnect()
return { exitCode, signal: null }
return this.terminationSignal === null
? { exitCode, signal: null }
: { exitCode: null, signal: this.terminationSignal }
}
const completed = await Promise.race([settlement, waitTick().then(() => undefined)])
if (completed !== undefined) return this.commandOutcome(completed)

View File

@@ -103,6 +103,7 @@ class FakeSandbox {
readonly signalErrors: unknown[] = []
trapsTerm = false
delaysKill = false
delaysKillCompletion = false
sdkKillStops = true
alive = true
ambient = 'PATH=/ambient/bin\0KEEP=safe\0NPM_TOKEN=secret\0DSH_STALE=old\0BROKEN\0=bad\0'
@@ -247,7 +248,7 @@ class FakeSandbox {
throw error
}
if (!this.delaysKill) this.alive = false
this.handle.fail(137)
if (!this.delaysKillCompletion) this.handle.fail(137)
return { exitCode: 0, stdout: '', stderr: '' }
}
if ((options as StartOptions | undefined)?.background === true) {
@@ -473,6 +474,25 @@ describe('E2BSubprocessHandle', () => {
expect(fake.handle.disconnects).toBe(0)
})
it('preserves a requested signal when output draining expires', async () => {
const fake = new FakeSandbox()
fake.trapsTerm = true
fake.delaysKill = true
fake.delaysKillCompletion = true
fake.sdkKillStops = false
const handle = new E2BSubprocessHandle(runtime(fake), spec({ graceMs: 5 }), '/runtime/drain-signal')
await flush()
handle.terminate()
await vi.waitFor(() => { expect(fake.commandsSeen).toContain('kill -KILL -- -4242') })
fake.exitStatus = '143\n'
await expect(handle.done).resolves.toEqual({ exitCode: null, signal: 'SIGKILL' })
expect(fake.handle.disconnects).toBe(1)
fake.alive = false
await expect(handle.waitForExit()).resolves.toBe(true)
})
it('rejects an invalid direct-command exit status', async () => {
const fake = new FakeSandbox()
const handle = new E2BSubprocessHandle(runtime(fake), spec(), '/runtime/invalid-status')