mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: add initDispatcher() for eager sidecar startup
The dispatcher was lazy-initialized on first AI request, but a race condition meant the first call always missed it (dispatcherReady still false) and fell through to cold per-request Python. initDispatcher() starts the dispatcher eagerly and returns a Promise that resolves with GPU status once ready (or after a timeout).
This commit is contained in:
@@ -1127,3 +1127,94 @@ describe("bridge - dispatcher lifecycle via runPythonWithProgress", () => {
|
||||
expect(callCount).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bridge - initDispatcher", () => {
|
||||
let initDispatcher: typeof import("../../../packages/ai/src/bridge.js").initDispatcher;
|
||||
let getDispatcherStatus: typeof import("../../../packages/ai/src/bridge.js").getDispatcherStatus;
|
||||
let shutdownDispatcher: typeof import("../../../packages/ai/src/bridge.js").shutdownDispatcher;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
vi.mocked(spawn).mockReset();
|
||||
|
||||
const mod = await import("../../../packages/ai/src/bridge.js");
|
||||
initDispatcher = mod.initDispatcher;
|
||||
getDispatcherStatus = mod.getDispatcherStatus;
|
||||
shutdownDispatcher = mod.shutdownDispatcher;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
shutdownDispatcher();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("resolves with ready=true and gpu status after dispatcher emits readiness", async () => {
|
||||
const mock = createMockProcess();
|
||||
vi.mocked(spawn).mockReturnValue(mock.process);
|
||||
|
||||
const promise = initDispatcher();
|
||||
|
||||
// Dispatcher emits readiness signal
|
||||
mock.stderr.emit("data", Buffer.from('{"ready": true, "gpu": true}\n'));
|
||||
|
||||
const result = await promise;
|
||||
expect(result).toEqual({ ready: true, gpu: true });
|
||||
expect(getDispatcherStatus().ready).toBe(true);
|
||||
expect(getDispatcherStatus().gpu).toBe(true);
|
||||
});
|
||||
|
||||
it("resolves with ready=false when dispatcher fails with ENOENT", async () => {
|
||||
const mock = createMockProcess();
|
||||
vi.mocked(spawn).mockReturnValue(mock.process);
|
||||
|
||||
const promise = initDispatcher();
|
||||
|
||||
const err = new Error("spawn ENOENT") as NodeJS.ErrnoException;
|
||||
err.code = "ENOENT";
|
||||
mock.emitEvent("error", err);
|
||||
|
||||
const result = await promise;
|
||||
expect(result).toEqual({ ready: false, gpu: false });
|
||||
});
|
||||
|
||||
it("resolves with ready=false after timeout when dispatcher never signals ready", async () => {
|
||||
vi.useFakeTimers();
|
||||
const mock = createMockProcess();
|
||||
vi.mocked(spawn).mockReturnValue(mock.process);
|
||||
|
||||
const promise = initDispatcher(500);
|
||||
|
||||
vi.advanceTimersByTime(600);
|
||||
|
||||
const result = await promise;
|
||||
expect(result).toEqual({ ready: false, gpu: false });
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("resolves with gpu=false when dispatcher reports no GPU", async () => {
|
||||
const mock = createMockProcess();
|
||||
vi.mocked(spawn).mockReturnValue(mock.process);
|
||||
|
||||
const promise = initDispatcher();
|
||||
|
||||
mock.stderr.emit("data", Buffer.from('{"ready": true, "gpu": false}\n'));
|
||||
|
||||
const result = await promise;
|
||||
expect(result).toEqual({ ready: true, gpu: false });
|
||||
});
|
||||
|
||||
it("is idempotent -- second call returns same result without respawning", async () => {
|
||||
const mock = createMockProcess();
|
||||
vi.mocked(spawn).mockReturnValue(mock.process);
|
||||
|
||||
const promise1 = initDispatcher();
|
||||
mock.stderr.emit("data", Buffer.from('{"ready": true, "gpu": true}\n'));
|
||||
await promise1;
|
||||
|
||||
const result2 = await initDispatcher();
|
||||
expect(result2).toEqual({ ready: true, gpu: true });
|
||||
// spawn should only have been called once
|
||||
expect(spawn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user