mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 00:04:01 +00:00
fix(runtime): stop one unreachable relay from freezing every workspace as active (STA-517) (#14649)
* fix(runtime): stop one unreachable relay from freezing every workspace as active The worktree.ps liveness refresh is the only thing that retires an exited PTY, and mobile renders "active" straight off the summary it produces. Its aggregate inventory ran every provider through Promise.all with no per-provider deadline, so a single SSH relay that rejected — or simply did not answer inside the 3s budget, since a relay list runs to the mux's own 30s default — cost the runtime the whole inventory. Nothing was ever proven dead, so every retained pane kept reporting hasHostSidebarActivity/liveTerminalCount, and the SSH workspaces stayed "active" on mobile for as long as the connection stayed unreachable. Settle each SSH provider independently and forward the caller's deadline, so local and healthy relays are still reconciled. A provider that does not answer is unknown, not empty: the runtime's existing hasPty rescue keeps its panes. A local failure still fails the aggregate, matching pty:listSessions. The restored-orchestration-authority sweep now runs after that rescue, so a pane the controller still vouches for keeps its handle instead of losing it to a listing that merely omitted it. STA-517 * test(runtime): assert the provider scope, not the exact arity, of inventory calls These assertions exist to prove which provider scope the inventory asked for. Forwarding the caller's deadline added a second argument, which broke them on arity alone. Match the scope argument and require a numeric deadline beside it, so the intent is preserved and the budget is covered too. * test(runtime): type the inventory mock's scope parameter A bare `async () =>` mock types mock.calls as an empty tuple, so reading the scope argument off it fails typecheck. Declare the parameter the runtime actually passes.
This commit is contained in:
@@ -1004,6 +1004,9 @@ async function referenceStatusFrameLines(
|
||||
}
|
||||
|
||||
const TEST_WINDOW_ID = 1
|
||||
// The inventory refresh forwards its own budget so a relay cannot outlive it (STA-517).
|
||||
// These assertions are about which provider scope was asked, so the deadline stays loose.
|
||||
const LIST_PROVIDER_DEADLINE = expect.objectContaining({ deadlineMs: expect.any(Number) })
|
||||
const TEST_REPO_ID = 'repo-1'
|
||||
const TEST_REPO_PATH = '/tmp/repo'
|
||||
const TEST_WORKTREE_PATH = '/tmp/worktree-a'
|
||||
@@ -20306,7 +20309,7 @@ describe('OrcaRuntimeService', () => {
|
||||
paneKey: makePaneKey('tab-agent', HEADLESS_LEAF_ID)
|
||||
})
|
||||
expect(listProcesses).toHaveBeenCalledTimes(inventoryCount + 1)
|
||||
expect(listProcesses).toHaveBeenLastCalledWith(null)
|
||||
expect(listProcesses).toHaveBeenLastCalledWith(null, LIST_PROVIDER_DEADLINE)
|
||||
expect(
|
||||
(await runtime.listTerminals()).terminals.find((terminal) => terminal.ptyId === 'pty-agent')
|
||||
).toMatchObject({
|
||||
@@ -20316,7 +20319,7 @@ describe('OrcaRuntimeService', () => {
|
||||
leafId: HEADLESS_LEAF_ID
|
||||
})
|
||||
expect(listProcesses).toHaveBeenCalledTimes(inventoryCount + 2)
|
||||
expect(listProcesses).toHaveBeenLastCalledWith(undefined)
|
||||
expect(listProcesses).toHaveBeenLastCalledWith(undefined, LIST_PROVIDER_DEADLINE)
|
||||
await expect(
|
||||
runtime.adoptTerminalOrphans({
|
||||
worktree: `id:${TEST_WORKTREE_ID}`,
|
||||
@@ -21582,7 +21585,7 @@ describe('OrcaRuntimeService', () => {
|
||||
expect(getSession().sleepingAgentSessionsByPaneKey?.[workerPaneKey]).toBeUndefined()
|
||||
expect(getSession().sleepingAgentSessionsByPaneKey?.[secondWorkerPaneKey]).toBeUndefined()
|
||||
expect(listProcesses).toHaveBeenCalledTimes(3)
|
||||
expect(listProcesses).toHaveBeenCalledWith(null)
|
||||
expect(listProcesses).toHaveBeenCalledWith(null, LIST_PROVIDER_DEADLINE)
|
||||
expect(getSession().tabsByWorktree[TEST_WORKTREE_ID]).toEqual([
|
||||
expect.objectContaining({ id: 'legacy-worker-two', ptyId: 'pty-exited-two' })
|
||||
])
|
||||
@@ -21697,7 +21700,7 @@ describe('OrcaRuntimeService', () => {
|
||||
await vi.advanceTimersByTimeAsync(1_000)
|
||||
|
||||
expect(listProcesses).toHaveBeenCalledTimes(4)
|
||||
expect(listProcesses.mock.calls).toEqual([[null], [null], [null], [null]])
|
||||
expect(listProcesses.mock.calls.map((call) => call[0])).toEqual([null, null, null, null])
|
||||
expect(hasPty).not.toHaveBeenCalled()
|
||||
expect(getSession().tabsByWorktree[TEST_WORKTREE_ID]).toEqual([
|
||||
expect.objectContaining({ id: 'legacy-worker', ptyId: 'pty-inventory-unavailable' })
|
||||
@@ -21888,7 +21891,7 @@ describe('OrcaRuntimeService', () => {
|
||||
deferredDispatchIds: ['dispatch-missing', 'dispatch-ambiguous']
|
||||
})
|
||||
expect(listProcesses).toHaveBeenCalledOnce()
|
||||
expect(listProcesses).toHaveBeenCalledWith(null)
|
||||
expect(listProcesses).toHaveBeenCalledWith(null, LIST_PROVIDER_DEADLINE)
|
||||
for (const { name, leafId } of cases.slice(0, 2)) {
|
||||
expect(
|
||||
getSession().sleepingAgentSessionsByPaneKey?.[`legacy-${name}:${leafId}`]
|
||||
@@ -21998,7 +22001,7 @@ describe('OrcaRuntimeService', () => {
|
||||
)
|
||||
expect(getSession().sleepingAgentSessionsByPaneKey?.[workerPaneKey]).toBeUndefined()
|
||||
expect(listProcesses).toHaveBeenCalledTimes(3)
|
||||
expect(listProcesses).toHaveBeenCalledWith(null)
|
||||
expect(listProcesses).toHaveBeenCalledWith(null, LIST_PROVIDER_DEADLINE)
|
||||
expect(revealTerminalSession).toHaveBeenCalledWith(TEST_FOLDER_WORKSPACE_KEY, {
|
||||
ptyId: 'pty-folder-legacy',
|
||||
title: 'Folder worker',
|
||||
@@ -22140,7 +22143,7 @@ describe('OrcaRuntimeService', () => {
|
||||
expect(getWorkspaceSession).toHaveBeenCalledWith(`ssh:${connectionId}`)
|
||||
expect(setWorkspaceSession).toHaveBeenCalledWith(expect.any(Object), `ssh:${connectionId}`)
|
||||
expect(listProcesses).toHaveBeenCalledTimes(3)
|
||||
expect(listProcesses).toHaveBeenCalledWith(connectionId)
|
||||
expect(listProcesses).toHaveBeenCalledWith(connectionId, LIST_PROVIDER_DEADLINE)
|
||||
expect(sshSession.tabsByWorktree[TEST_FOLDER_WORKSPACE_KEY]).toContainEqual(
|
||||
expect.objectContaining({
|
||||
id: 'legacy-ssh-folder-worker',
|
||||
@@ -22369,7 +22372,7 @@ describe('OrcaRuntimeService', () => {
|
||||
exitedDispatchIds: [],
|
||||
deferredDispatchIds: []
|
||||
})
|
||||
expect(listProcesses).toHaveBeenLastCalledWith(connectionId)
|
||||
expect(listProcesses).toHaveBeenLastCalledWith(connectionId, LIST_PROVIDER_DEADLINE)
|
||||
} finally {
|
||||
unregisterSshGitProvider(connectionId)
|
||||
}
|
||||
@@ -22455,7 +22458,9 @@ describe('OrcaRuntimeService', () => {
|
||||
}
|
||||
]
|
||||
} as unknown as OrchestrationDb)
|
||||
const listProcesses = vi.fn(async () => [
|
||||
// Declares the scope parameter so mock.calls keeps it — the runtime passes a deadline
|
||||
// alongside it, and a bare `async () =>` would type the call tuple as empty.
|
||||
const listProcesses = vi.fn(async (_connectionId?: string | null) => [
|
||||
{
|
||||
id: 'pty-wsl-legacy',
|
||||
incarnationId,
|
||||
@@ -22506,7 +22511,7 @@ describe('OrcaRuntimeService', () => {
|
||||
expect(getSession().sleepingAgentSessionsByPaneKey?.[workerPaneKey]).toBeUndefined()
|
||||
expect(revealTerminalSession).toHaveBeenCalledOnce()
|
||||
expect(listProcesses).toHaveBeenCalledTimes(5)
|
||||
expect(listProcesses.mock.calls).toEqual([[null], [null], [null], [null], [null]])
|
||||
expect(listProcesses.mock.calls.map((call) => call[0])).toEqual([null, null, null, null, null])
|
||||
})
|
||||
|
||||
it('restores orphan pane and group topology without replacing a newer host-owned tab', async () => {
|
||||
|
||||
Reference in New Issue
Block a user