fix(anime): honor the sidecar readiness deadline and confirm shutdown

The capabilities probe used a fixed 5s timeout, so a readiness budget
shorter than that could be overrun by one stalled request. Pass the
remaining deadline down instead.

stop() also resolved after the SIGKILL wait even when the child had not
exited, letting a restart race a process still holding the port. Throw
in that case, and keep a failed shutdown from masking the readiness
error at startup.
This commit is contained in:
2026-07-31 18:07:32 -07:00
parent ca5fc341cb
commit 301714777a
4 changed files with 53 additions and 8 deletions
+21 -2
View File
@@ -11,9 +11,16 @@ const binaries: BundleBinaries = {
};
/** A ChildProcess stand-in: an EventEmitter with the bits startSidecar touches. */
function fakeChild(): ChildProcess {
function fakeChild(onKill?: (child: EventEmitter) => void): ChildProcess {
const child = new EventEmitter();
Object.assign(child, { stdout: null, stderr: null, kill: () => true });
Object.assign(child, {
stdout: null,
stderr: null,
kill: () => {
onKill?.(child);
return true;
},
});
return child as unknown as ChildProcess;
}
@@ -32,6 +39,18 @@ test('a failed spawn rejects instead of throwing an unhandled error event', asyn
);
});
test('a readiness timeout shuts the child down and reports the timeout', async () => {
const port = await allocatePort();
// Never becomes ready, but does go down on the first signal.
const child = fakeChild((emitter) => queueMicrotask(() => emitter.emit('exit', 0, 'SIGTERM')));
const spawnImpl = (() => child) as unknown as typeof spawnType;
await assert.rejects(
() => startSidecar({ binaries, port, readyTimeoutMs: 50, spawnImpl }),
/did not become ready within 50ms/,
);
});
test('an early exit is reported with its code rather than waiting out the deadline', async () => {
const port = await allocatePort();
const child = fakeChild();