mirror of
https://github.com/stablyai/orca.git
synced 2026-10-26 16:02:42 +00:00
fix(runtime): settle renderer terminal close race
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user