fix(terminal): preserve teardown ownership until confirmed

This commit is contained in:
Brennan Benson
2026-07-12 16:31:19 -07:00
parent 8ea0622587
commit 3dd79e7433
15 changed files with 455 additions and 145 deletions
@@ -16,7 +16,7 @@ describe('PTY kill retry ownership', () => {
vi.restoreAllMocks()
})
it('bounds retained ids and retries at most one per lifecycle event', async () => {
it('retains every live id and retries at most one per lifecycle event', async () => {
const kill = vi.fn().mockRejectedValue(new Error('provider disconnected'))
vi.stubGlobal('window', { api: { pty: { kill } } })
vi.spyOn(console, 'warn').mockImplementation(() => {})
@@ -26,12 +26,15 @@ describe('PTY kill retry ownership', () => {
}
kill.mockResolvedValue(undefined)
retryRetainedPtyKills()
await Promise.resolve()
await Promise.resolve()
for (const _id of IDS) {
retryRetainedPtyKills()
await Promise.resolve()
await Promise.resolve()
}
expect(kill).toHaveBeenCalledTimes(66)
expect(kill.mock.calls.filter(([id]) => id === IDS[0])).toHaveLength(1)
expect(kill.mock.calls.filter(([id]) => id === IDS[1])).toHaveLength(2)
expect(kill).toHaveBeenCalledTimes(IDS.length * 2)
for (const id of IDS) {
expect(kill.mock.calls.filter(([calledId]) => calledId === id)).toHaveLength(2)
}
})
})
@@ -11,7 +11,6 @@ export type PtyKillIdentity = {
expectedTabId?: string
}
const MAX_RETAINED_PTY_KILLS = 64
const MAX_RETRY_BACKOFF_MS = 30_000
const retainedPtyKills = new Map<string, RetainedPtyKill>()
@@ -26,13 +25,9 @@ export function killPtyRetainingRetryOwnership(
): Promise<void> {
let retained = retainedPtyKills.get(id)
if (!retained) {
if (retainedPtyKills.size >= MAX_RETAINED_PTY_KILLS) {
const oldestId = retainedPtyKills.keys().next().value
if (typeof oldestId === 'string') {
retainedPtyKills.delete(oldestId)
console.warn('[pty] Dropped oldest retained shutdown retry at bounded capacity')
}
}
// Why: every entry represents a provider process whose shutdown is still
// unconfirmed. Dropping one to cap bookkeeping would orphan the real PTY;
// repeated failures for the same PTY still coalesce into this single record.
retained = { diagnostic, inFlight: null, options, attempts: 0, nextRetryAt: 0 }
}
retained.diagnostic = diagnostic