mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: do not count normal dispatcher exits as crashes
The close handler called recordCrash() unconditionally, even for exit code 0 (normal MAX_REQUESTS restart). After 5 normal cycles within 60s the dispatcher was permanently disabled. Now only non-zero exits count.
This commit is contained in:
@@ -216,12 +216,14 @@ function startDispatcher(): ChildProcess | null {
|
||||
dispatcherReady = false;
|
||||
});
|
||||
|
||||
child.on("close", () => {
|
||||
child.on("close", (code) => {
|
||||
for (const [id, req] of pendingRequests.entries()) {
|
||||
req.reject(new Error("Python dispatcher exited unexpectedly"));
|
||||
pendingRequests.delete(id);
|
||||
}
|
||||
if (code !== 0) {
|
||||
recordCrash();
|
||||
}
|
||||
dispatcher = null;
|
||||
dispatcherReady = false;
|
||||
});
|
||||
|
||||
@@ -1035,6 +1035,59 @@ describe("bridge - dispatcher lifecycle via runPythonWithProgress", () => {
|
||||
// The important thing is no crash -- the line is collected for pending requests
|
||||
});
|
||||
|
||||
it("does not count exit code 0 as a crash (normal MAX_REQUESTS restart)", async () => {
|
||||
const mockDispatcher = createMockProcess();
|
||||
const mockPerReq = createMockProcess();
|
||||
let callCount = 0;
|
||||
|
||||
vi.mocked(spawn).mockImplementation(() => {
|
||||
callCount++;
|
||||
if (callCount === 1) return mockDispatcher.process;
|
||||
return mockPerReq.process;
|
||||
});
|
||||
|
||||
const promise = runPythonWithProgress("test.py", []);
|
||||
|
||||
// Dispatcher exits with code 0 (normal MAX_REQUESTS shutdown)
|
||||
mockDispatcher.emitEvent("close", 0, null);
|
||||
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
|
||||
mockPerReq.stdout.emit("data", Buffer.from('{"ok": true}\n'));
|
||||
mockPerReq.emitEvent("close", 0, null);
|
||||
await promise;
|
||||
|
||||
const status = getDispatcherStatus();
|
||||
expect(status.consecutiveCrashes).toBe(0);
|
||||
expect(status.failed).toBe(false);
|
||||
});
|
||||
|
||||
it("still counts non-zero exit codes as crashes", async () => {
|
||||
const mockDispatcher = createMockProcess();
|
||||
const mockPerReq = createMockProcess();
|
||||
let callCount = 0;
|
||||
|
||||
vi.mocked(spawn).mockImplementation(() => {
|
||||
callCount++;
|
||||
if (callCount === 1) return mockDispatcher.process;
|
||||
return mockPerReq.process;
|
||||
});
|
||||
|
||||
const promise = runPythonWithProgress("test.py", []);
|
||||
|
||||
// Dispatcher exits with code 1 (real crash)
|
||||
mockDispatcher.emitEvent("close", 1, null);
|
||||
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
|
||||
mockPerReq.stdout.emit("data", Buffer.from('{"ok": true}\n'));
|
||||
mockPerReq.emitEvent("close", 0, null);
|
||||
await promise;
|
||||
|
||||
const status = getDispatcherStatus();
|
||||
expect(status.consecutiveCrashes).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("per-request fallback retries with python3 when venv python fails with ENOENT", async () => {
|
||||
const mockDispatcher = createMockProcess();
|
||||
const mockVenvPython = createMockProcess();
|
||||
|
||||
Reference in New Issue
Block a user