test: cover exact-id reload cancellation
This commit is contained in:
@@ -484,12 +484,8 @@ export class AgentLoop extends Service implements AgentFactory {
|
|||||||
released.resolve()
|
released.resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const disposeAgentListener = ownerCtx.on('agent/disposed', (agent) => {
|
const disposeAgentListener = ownerCtx.on('agent/disposed', checkReleased)
|
||||||
if (agent.id === sessionId) checkReleased()
|
const disposeSessionListener = ownerCtx.on('session/disposed', checkReleased)
|
||||||
})
|
|
||||||
const disposeSessionListener = ownerCtx.on('session/disposed', (session) => {
|
|
||||||
if (session.id === sessionId) checkReleased()
|
|
||||||
})
|
|
||||||
try {
|
try {
|
||||||
checkReleased()
|
checkReleased()
|
||||||
await this.ownership.waitWhileActive(released.promise)
|
await this.ownership.waitWhileActive(released.promise)
|
||||||
|
|||||||
@@ -136,6 +136,37 @@ describe('config-driven session id', () => {
|
|||||||
await ctx.fiber.dispose()
|
await ctx.fiber.dispose()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('cancels an exact-id reload while the prior lifecycle is still draining', async () => {
|
||||||
|
const root = await mkdtemp(join(tmpdir(), 'dsh-cfg-exact-cancel-'))
|
||||||
|
dirs.push(root)
|
||||||
|
const ctx = await makeCoreContext()
|
||||||
|
await ctx.plugin(SessionPersistenceJsonl, { root })
|
||||||
|
const sessionId = SessionId('stdio-exact-cancel')
|
||||||
|
const config = { agents: [{ id: 'main', sessionId, model: 'mock' }] }
|
||||||
|
const firstLoop = await ctx.plugin(AgentLoop, config)
|
||||||
|
await expect.poll(() => ctx.agents.get(sessionId)).toBeDefined()
|
||||||
|
const first = ctx.agents.get(sessionId) as ReactLoopAgent
|
||||||
|
|
||||||
|
const flushGate = Promise.withResolvers<undefined>()
|
||||||
|
ctx.on('session/flush', (session) => {
|
||||||
|
if (session === first.session) return flushGate.promise
|
||||||
|
})
|
||||||
|
first.inject([{ type: 'text', text: 'persist before cancellation' }], {
|
||||||
|
source: { kind: 'plugin', plugin: 'test' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const firstDisposal = firstLoop.dispose()
|
||||||
|
await expect.poll(() => first.status).toBe('disposed')
|
||||||
|
const secondLoop = await ctx.plugin(AgentLoop, config)
|
||||||
|
await secondLoop.dispose()
|
||||||
|
expect(ctx.agents.get(sessionId)).toBe(first)
|
||||||
|
|
||||||
|
flushGate.resolve(undefined)
|
||||||
|
await firstDisposal
|
||||||
|
expect(ctx.agents.get(sessionId)).toBeUndefined()
|
||||||
|
await ctx.fiber.dispose()
|
||||||
|
})
|
||||||
|
|
||||||
it('contains an exact-id persistence lookup failure', async () => {
|
it('contains an exact-id persistence lookup failure', async () => {
|
||||||
const root = await mkdtemp(join(tmpdir(), 'dsh-cfg-exact-failure-'))
|
const root = await mkdtemp(join(tmpdir(), 'dsh-cfg-exact-failure-'))
|
||||||
dirs.push(root)
|
dirs.push(root)
|
||||||
@@ -208,33 +239,37 @@ describe('config-driven session id', () => {
|
|||||||
await ctx.fiber.dispose()
|
await ctx.fiber.dispose()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('joins an exact-id persistence lookup before AgentLoop disposal completes', async () => {
|
it.each(['resolve', 'reject'] as const)(
|
||||||
const root = await mkdtemp(join(tmpdir(), 'dsh-cfg-exact-dispose-'))
|
'joins an exact-id persistence lookup that will %s before AgentLoop disposal completes',
|
||||||
dirs.push(root)
|
async (outcome) => {
|
||||||
const ctx = await makeCoreContext()
|
const root = await mkdtemp(join(tmpdir(), 'dsh-cfg-exact-dispose-'))
|
||||||
await ctx.plugin(SessionPersistenceJsonl, { root })
|
dirs.push(root)
|
||||||
const listing = Promise.withResolvers<Awaited<ReturnType<typeof ctx.sessionPersistence.list>>>()
|
const ctx = await makeCoreContext()
|
||||||
vi.spyOn(ctx.sessionPersistence, 'list').mockReturnValue(listing.promise)
|
await ctx.plugin(SessionPersistenceJsonl, { root })
|
||||||
const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => undefined)
|
const listing = Promise.withResolvers<Awaited<ReturnType<typeof ctx.sessionPersistence.list>>>()
|
||||||
const failures: unknown[] = []
|
vi.spyOn(ctx.sessionPersistence, 'list').mockReturnValue(listing.promise)
|
||||||
ctx.on('agent-loop/config-start-failed', (_sessionId, error) => { failures.push(error) })
|
const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => undefined)
|
||||||
|
const failures: unknown[] = []
|
||||||
|
ctx.on('agent-loop/config-start-failed', (_sessionId, error) => { failures.push(error) })
|
||||||
|
|
||||||
const loop = await ctx.plugin(AgentLoop, {
|
const loop = await ctx.plugin(AgentLoop, {
|
||||||
agents: [{ id: 'main', sessionId: SessionId('stdio-exact-dispose'), model: 'mock' }],
|
agents: [{ id: 'main', sessionId: SessionId('stdio-exact-dispose'), model: 'mock' }],
|
||||||
})
|
})
|
||||||
let disposed = false
|
let disposed = false
|
||||||
const disposal = loop.dispose().then(() => { disposed = true })
|
const disposal = loop.dispose().then(() => { disposed = true })
|
||||||
await Promise.resolve()
|
await Promise.resolve()
|
||||||
expect(disposed).toBe(false)
|
expect(disposed).toBe(false)
|
||||||
|
|
||||||
listing.reject(new Error('startup cancelled by teardown'))
|
if (outcome === 'resolve') listing.resolve([])
|
||||||
await disposal
|
else listing.reject(new Error('startup cancelled by teardown'))
|
||||||
expect(ctx.agents.get(SessionId('stdio-exact-dispose'))).toBeUndefined()
|
await disposal
|
||||||
expect(failures).toEqual([])
|
expect(ctx.agents.get(SessionId('stdio-exact-dispose'))).toBeUndefined()
|
||||||
expect(warn).not.toHaveBeenCalled()
|
expect(failures).toEqual([])
|
||||||
warn.mockRestore()
|
expect(warn).not.toHaveBeenCalled()
|
||||||
await ctx.fiber.dispose()
|
warn.mockRestore()
|
||||||
})
|
await ctx.fiber.dispose()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
it('identity-nests the deferred resume fiber under its labeled owner effect', async () => {
|
it('identity-nests the deferred resume fiber under its labeled owner effect', async () => {
|
||||||
const ctx = new Context()
|
const ctx = new Context()
|
||||||
|
|||||||
Reference in New Issue
Block a user