fix(orchestration): treat ask as a long-poll so it survives the 30s socket idle wall (#9351)
orchestration.ask blocks server-side until a reply lands or its timeout
(default 600s) elapses, holding the RPC open — but isLongPollRequest never
classified it as a long-poll. So the keepalive that resets the 30s
RUNTIME_RPC_SOCKET_IDLE_TIMEOUT_MS was never armed, and any ask left
unanswered for 30s died with a misleading runtime_unavailable ("The Orca
runtime closed the connection"), regardless of --timeout-ms. The same
omission left the handler's abort signal unwired (it is only passed for
long-polls), so the client-disconnect release path guarded by
signal?.aborted was dead code.
Add orchestration.ask to isLongPollRequest so it gets the keepalive, the
abort signal, and long-poll admission. The client already extends its
per-call socket timeout for ask (handlers/orchestration.ts passes
timeoutMs + 5s at the call site), so only the server-side classification
was missing.
This commit is contained in:
parent
143d2232bb
commit
cd28da13f4
|
|
@ -3909,6 +3909,51 @@ describe('OrcaRuntimeRpcServer', () => {
|
|||
}
|
||||
})
|
||||
|
||||
it('emits keepalive frames while orchestration.ask blocks for a reply', async () => {
|
||||
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-rpc-'))
|
||||
const runtime = new OrcaRuntimeService()
|
||||
const db = new OrchestrationDb(':memory:')
|
||||
runtime.setOrchestrationDb(db)
|
||||
const server = new OrcaRuntimeRpcServer({
|
||||
runtime,
|
||||
userDataPath,
|
||||
keepaliveIntervalMs: 50
|
||||
})
|
||||
await server.start()
|
||||
|
||||
try {
|
||||
const metadata = readRuntimeMetadata(userDataPath)
|
||||
// Why: no reply is ever sent, so ask blocks the full window on the same
|
||||
// hold-the-socket path check --wait uses. Without ask in the long-poll
|
||||
// set the 30s idle timer would tear this down before it keepalives.
|
||||
const session = openFramedSession(metadata!.transports[0]!.endpoint, {
|
||||
id: 'req_ask',
|
||||
authToken: metadata!.authToken,
|
||||
method: 'orchestration.ask',
|
||||
params: {
|
||||
to: 'term_nobody',
|
||||
from: 'term_asker',
|
||||
question: 'ping?',
|
||||
timeoutMs: 300
|
||||
}
|
||||
})
|
||||
await session.done
|
||||
|
||||
const keepalives = session.frames.filter((f) => f._keepalive === true)
|
||||
const terminals = session.frames.filter((f) => f.ok !== undefined)
|
||||
expect(terminals).toHaveLength(1)
|
||||
expect(terminals[0]).toMatchObject({
|
||||
id: 'req_ask',
|
||||
ok: true,
|
||||
result: { timedOut: true }
|
||||
})
|
||||
expect(keepalives.length).toBeGreaterThanOrEqual(3)
|
||||
} finally {
|
||||
db.close()
|
||||
await server.stop()
|
||||
}
|
||||
})
|
||||
|
||||
it('emits keepalive frames while terminal.wait blocks and returns its structured timeout', async () => {
|
||||
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-rpc-'))
|
||||
const runtime = new OrcaRuntimeService()
|
||||
|
|
|
|||
|
|
@ -397,6 +397,14 @@ function isLongPollRequest(request: RpcRequest): boolean {
|
|||
if (request.method === 'terminal.wait') {
|
||||
return true
|
||||
}
|
||||
// Why: orchestration.ask blocks unconditionally (default 600 s) holding the
|
||||
// RPC open until a reply lands or the deadline passes, so it needs the same
|
||||
// keepalive as check --wait or the 30 s socket idle timer tears it down. It
|
||||
// also relies on the abort signal (only wired for long-polls) to release the
|
||||
// waiter when the asking client disconnects.
|
||||
if (request.method === 'orchestration.ask') {
|
||||
return true
|
||||
}
|
||||
if (request.method === 'orchestration.check') {
|
||||
const params = request.params as { wait?: unknown } | undefined
|
||||
return params?.wait === true
|
||||
|
|
|
|||
Loading…
Reference in New Issue