mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 16:03:26 +00:00
fix: time out daemon hello handshakes
This commit is contained in:
@@ -49,8 +49,10 @@ describe('DaemonClient', () => {
|
||||
|
||||
function startMockDaemon(opts?: {
|
||||
onControlMessage?: (msg: unknown) => string | null
|
||||
onHello?: (msg: HelloMessage) => void
|
||||
onStreamHello?: (msg: HelloMessage) => void
|
||||
rejectVersion?: boolean
|
||||
suppressHelloResponse?: boolean
|
||||
}): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
server = createServer((socket) => {
|
||||
@@ -69,6 +71,10 @@ describe('DaemonClient', () => {
|
||||
|
||||
if (msg.type === 'hello') {
|
||||
const hello = msg as HelloMessage
|
||||
opts?.onHello?.(hello)
|
||||
if (opts?.suppressHelloResponse) {
|
||||
return
|
||||
}
|
||||
if (opts?.rejectVersion) {
|
||||
socket.write(encodeNdjson({ type: 'hello', ok: false, error: 'Version mismatch' }))
|
||||
return
|
||||
@@ -130,6 +136,34 @@ describe('DaemonClient', () => {
|
||||
client = new DaemonClient({ socketPath, tokenPath })
|
||||
await expect(client.ensureConnected()).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('times out when the daemon never answers hello', async () => {
|
||||
let resolveHello: () => void = () => {}
|
||||
const helloReceived = new Promise<void>((resolve) => {
|
||||
resolveHello = resolve
|
||||
})
|
||||
await startMockDaemon({
|
||||
suppressHelloResponse: true,
|
||||
onHello: resolveHello
|
||||
})
|
||||
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
client = new DaemonClient({ socketPath, tokenPath })
|
||||
const outcomePromise = client
|
||||
.ensureConnected()
|
||||
.then(() => 'connected')
|
||||
.catch((error: Error) => error.message)
|
||||
|
||||
await helloReceived
|
||||
await vi.advanceTimersByTimeAsync(5000)
|
||||
const outcome = await Promise.race([outcomePromise, Promise.resolve('pending')])
|
||||
|
||||
expect(outcome).toBe('Hello response timed out')
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('RPC', () => {
|
||||
|
||||
@@ -214,6 +214,29 @@ export class DaemonClient {
|
||||
}
|
||||
|
||||
let buffer = ''
|
||||
let settled = false
|
||||
let timer: ReturnType<typeof setTimeout> | null = null
|
||||
const cleanup = (): void => {
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
timer = null
|
||||
}
|
||||
socket.removeListener('data', onData)
|
||||
socket.removeListener('error', onError)
|
||||
socket.removeListener('close', onClose)
|
||||
}
|
||||
const finish = (error?: Error): void => {
|
||||
if (settled) {
|
||||
return
|
||||
}
|
||||
settled = true
|
||||
cleanup()
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
}
|
||||
const onData = (chunk: Buffer): void => {
|
||||
buffer += chunk.toString()
|
||||
const newlineIdx = buffer.indexOf('\n')
|
||||
@@ -221,23 +244,33 @@ export class DaemonClient {
|
||||
return
|
||||
}
|
||||
|
||||
socket.removeListener('data', onData)
|
||||
const line = buffer.slice(0, newlineIdx)
|
||||
try {
|
||||
const response = JSON.parse(line) as HelloResponse
|
||||
if (response.ok) {
|
||||
resolve()
|
||||
finish()
|
||||
} else {
|
||||
reject(
|
||||
finish(
|
||||
new DaemonProtocolError(addNodePtyRecoveryHint(response.error ?? 'Hello rejected'))
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
reject(new DaemonProtocolError('Invalid hello response'))
|
||||
finish(new DaemonProtocolError('Invalid hello response'))
|
||||
}
|
||||
}
|
||||
const onError = (error: Error): void => finish(error)
|
||||
const onClose = (): void =>
|
||||
finish(new DaemonProtocolError('Connection closed before hello response'))
|
||||
|
||||
timer = setTimeout(() => {
|
||||
// Why: a stale daemon can accept the socket but never answer hello;
|
||||
// without a handshake timeout, startup waits forever on ensureConnected().
|
||||
finish(new DaemonProtocolError('Hello response timed out'))
|
||||
socket.destroy()
|
||||
}, CONNECT_TIMEOUT_MS)
|
||||
socket.on('data', onData)
|
||||
socket.on('error', onError)
|
||||
socket.on('close', onClose)
|
||||
socket.write(encodeNdjson(hello))
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user