Fix timer bounds and ACP teardown ownership
This commit is contained in:
@@ -7,6 +7,7 @@ import { join, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import SubagentService from '@deepseek-ai/dsh-subagent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
|
||||
import * as acp from '../src/index.ts'
|
||||
import { acpStopReason, acpContentText, DEFAULT_DISPOSE_EOF_GRACE_MS, DEFAULT_DISPOSE_GRACE_MS, disposeAcpChild, startAcpRun, toAcpPrompt, type AcpRunSpec } from '../src/run.ts'
|
||||
import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
|
||||
@@ -147,7 +148,7 @@ describe('disposeAcpChild (the backend-owned teardown ladder over seam verbs)',
|
||||
|
||||
it('tier 1: a cooperative child exits on stdin EOF without any signal', async () => {
|
||||
const child = bash('read -r line; exit 0')
|
||||
await disposeAcpChild(child, 5_000, 200)
|
||||
await disposeAcpChild(child, 5_000)
|
||||
const outcome = await child.done
|
||||
expect(outcome.exitCode).toBe(0)
|
||||
expect(outcome.signal).toBeNull()
|
||||
@@ -155,7 +156,7 @@ describe('disposeAcpChild (the backend-owned teardown ladder over seam verbs)',
|
||||
|
||||
it('tier 2: an EOF-deaf child dies by the terminate escalation (SIGTERM)', async () => {
|
||||
const child = bash('sleep 60')
|
||||
await disposeAcpChild(child, 100, 5_000)
|
||||
await disposeAcpChild(child, 100)
|
||||
const outcome = await child.done
|
||||
expect(outcome.signal).toBe('SIGTERM')
|
||||
})
|
||||
@@ -166,30 +167,11 @@ describe('disposeAcpChild (the backend-owned teardown ladder over seam verbs)',
|
||||
while (!child.collected.stdout!.readFrom(0).text.includes('armed')) {
|
||||
await new Promise(resolve => setTimeout(resolve, 10))
|
||||
}
|
||||
await disposeAcpChild(child, 50, 2_000)
|
||||
await disposeAcpChild(child, 50)
|
||||
const outcome = await child.done
|
||||
expect(outcome.signal).toBe('SIGKILL')
|
||||
})
|
||||
|
||||
it('throws when the tree survives even the escalation window', async () => {
|
||||
// A handle whose tree never exits (waitForExit only ever aborts): the
|
||||
// ladder must fail loud instead of resolving over survivors. Built as a
|
||||
// stub because the ladder composes only public verbs.
|
||||
const never: Parameters<typeof disposeAcpChild>[0] = {
|
||||
pid: 1,
|
||||
stdin: undefined,
|
||||
stdout: undefined,
|
||||
stderr: undefined,
|
||||
collected: {},
|
||||
done: new Promise(() => {}),
|
||||
terminate: () => {},
|
||||
waitForExit: (signal?: AbortSignal) => new Promise((resolve) => {
|
||||
signal?.addEventListener('abort', () => { resolve(false) }, { once: true })
|
||||
}),
|
||||
}
|
||||
await expect(disposeAcpChild(never, 20, 20)).rejects.toThrow(/did not exit within its dispose windows/)
|
||||
})
|
||||
|
||||
it('observes a spawn-level rejection and returns without a process to reap', async () => {
|
||||
const child = spawnSubprocess({
|
||||
argv: ['bash', '-c', 'true'],
|
||||
@@ -197,7 +179,7 @@ describe('disposeAcpChild (the backend-owned teardown ladder over seam verbs)',
|
||||
stdio: { stdin: 'ignore', stdout: { maxBytes: 1000 }, stderr: { maxBytes: 1000 } },
|
||||
graceMs: 200,
|
||||
})
|
||||
await expect(disposeAcpChild(child, 1_000, 1_000)).resolves.toBeUndefined()
|
||||
await expect(disposeAcpChild(child, 1_000)).resolves.toBeUndefined()
|
||||
await expect(child.done).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
@@ -721,13 +703,20 @@ describe('dsh-subagent-acp', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects a non-positive dispose grace at load', async () => {
|
||||
for (const bad of [{ disposeEofGraceMs: 0 }, { disposeGraceMs: -1 }, { disposeEofGraceMs: Number.NaN }]) {
|
||||
it('rejects a dispose grace outside the Node timer range at load', async () => {
|
||||
for (const bad of [
|
||||
{ disposeEofGraceMs: 0 },
|
||||
{ disposeGraceMs: -1 },
|
||||
{ disposeEofGraceMs: Number.NaN },
|
||||
{ disposeGraceMs: Number.POSITIVE_INFINITY },
|
||||
{ disposeEofGraceMs: MAX_TIMER_DELAY_MS + 1 },
|
||||
{ disposeGraceMs: MAX_TIMER_DELAY_MS + 1 },
|
||||
]) {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SubagentService)
|
||||
await ctx.plugin(LocalSubprocessService)
|
||||
await expect(ctx.plugin(acp, { providerName: 'acp', command: 'true', args: [], permission: 'reject', env: {}, ...bad }))
|
||||
.rejects.toThrow(/subagent-acp: dispose(?:Eof)?GraceMs must be a positive finite number/)
|
||||
.rejects.toThrow(new RegExp(`subagent-acp: dispose(?:Eof)?GraceMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`))
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user