fix(runtime): settle renderer terminal close race

This commit is contained in:
OrcaWin
2026-08-20 12:23:28 -07:00
parent 240664ada0
commit ab377d6162
2 changed files with 100 additions and 5 deletions
+80
View File
@@ -31451,6 +31451,86 @@ describe('OrcaRuntimeService', () => {
expect((await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)).tabs).toEqual([])
})
it('settles a renderer-owned close when another transaction retires its surface first', async () => {
const ptyId = 'renderer-worker-retired-tab'
const { runtimeStore, getSession } = makeRuntimeStoreWithWorkspaceSession(
makeWorkspaceSessionWithHeadlessTerminal({
tabsByWorktree: {
[TEST_WORKTREE_ID]: [
{
id: 'host-tab',
ptyId,
worktreeId: TEST_WORKTREE_ID,
title: 'Renderer worker',
customTitle: null,
color: null,
sortOrder: 0,
createdAt: 1
}
]
},
terminalLayoutsByTabId: {
'host-tab': makeHeadlessTerminalLayout({ [HEADLESS_LEAF_ID]: ptyId })
}
})
)
const kill = vi.fn(() => true)
const closeTerminalTab = vi.fn(async () => {})
const runtime = new OrcaRuntimeService(runtimeStore as never)
runtime.setNotifier({ closeTerminalTab } as never)
runtime.setPtyController({
write: () => true,
kill,
getForegroundProcess: async () => null,
listProcesses: async () => []
})
runtime.registerPty(ptyId, TEST_WORKTREE_ID, null, {
tabId: 'host-tab',
leafId: HEADLESS_LEAF_ID
})
runtime.syncWindowGraph(1, {
tabs: [
{
tabId: 'host-tab',
worktreeId: TEST_WORKTREE_ID,
title: 'Renderer worker',
activeLeafId: HEADLESS_LEAF_ID,
layout: null
}
],
leaves: [
{
tabId: 'host-tab',
worktreeId: TEST_WORKTREE_ID,
leafId: HEADLESS_LEAF_ID,
paneRuntimeId: 1,
ptyId
}
]
})
const listed = await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)
const terminal = listed.tabs.find((tab) => tab.type === 'terminal')
if (!terminal || terminal.type !== 'terminal' || !terminal.terminal) {
throw new Error('Expected a ready terminal')
}
const closeMobileSessionTab = runtime.closeMobileSessionTab.bind(runtime)
vi.spyOn(runtime, 'closeMobileSessionTab').mockImplementation(async (...args) => {
runtime.syncWindowGraph(1, { tabs: [], leaves: [] })
await closeMobileSessionTab(...args)
throw new Error('tab_not_found')
})
await expect(runtime.closeTerminal(terminal.terminal)).resolves.toEqual({
handle: terminal.terminal,
tabId: 'host-tab',
ptyKilled: true
})
expect(kill).toHaveBeenCalledWith(ptyId)
expect(closeTerminalTab).not.toHaveBeenCalled()
expect(getSession().tabsByWorktree[TEST_WORKTREE_ID]).toEqual([])
expect((await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)).tabs).toEqual([])
})
it('does not mask a missing durable tab while its headless surface remains live', async () => {
const ptyId = 'headless-worker-live-tab'
const { runtimeStore } = makeRuntimeStoreWithWorkspaceSession(
+20 -5
View File
@@ -29185,6 +29185,17 @@ export class OrcaRuntimeService {
return surface
}
private isAlreadyRetiredMobileTerminalSurfaceError(
error: Error,
worktreeId: string,
parentTabId: string
): boolean {
// Why: missing persistence is ambiguous while a sibling or stale live publication still owns the surface.
return (
error.message === 'tab_not_found' && !this.findMobileTerminalSurface(worktreeId, parentTabId)
)
}
private findMobileTerminalSurfaceForPty(
worktreeId: string,
ptyId: string
@@ -29799,10 +29810,16 @@ export class OrcaRuntimeService {
localPtyTeardownOwnedExternally: true
})
} catch (error) {
if (!(error instanceof Error) || error.message !== 'workspace_session_unavailable') {
if (!(error instanceof Error)) {
throw error
}
if (error.message === 'workspace_session_unavailable') {
this.notifier.closeTerminal?.(tabId)
} else if (
!this.isAlreadyRetiredMobileTerminalSurfaceError(error, pty.pty.worktreeId, tabId)
) {
throw error
}
this.notifier.closeTerminal?.(tabId)
}
const ptyKilled = await this.stopExplicitlyClosedTabPtys(ptyIdsToKill, pty.pty.ptyId)
return this.describeTerminalClose(handle, tabId, pty.pty.ptyId, ptyKilled)
@@ -29826,10 +29843,8 @@ export class OrcaRuntimeService {
if (error.message === 'workspace_session_unavailable') {
this.notifier?.closeTerminal(tabId)
} else if (
error.message !== 'tab_not_found' ||
this.findMobileTerminalSurface(pty.pty.worktreeId, tabId)
!this.isAlreadyRetiredMobileTerminalSurfaceError(error, pty.pty.worktreeId, tabId)
) {
// Only absence in both persistence and the live publication proves another owner finished teardown.
throw error
}
}