From d60ee6bb1c701e97a7453fe269c0deb2c54cc165 Mon Sep 17 00:00:00 2001 From: trvon Date: Tue, 22 Sep 2026 14:24:33 -0600 Subject: [PATCH] fix(tasks): unref the pi-tasks detection timer so pi -p can exit Without pi-tasks installed nothing answers the detection ping, so the 5s window timer kept one-shot pi -p processes alive ~4s after the response. --- src/runtime/task-rpc.ts | 3 +++ test/task-rpc.test.ts | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/runtime/task-rpc.ts b/src/runtime/task-rpc.ts index d39779d..58816c6 100644 --- a/src/runtime/task-rpc.ts +++ b/src/runtime/task-rpc.ts @@ -65,6 +65,9 @@ export function createTaskRuntimeBridge(options: TaskRuntimeBridgeOptions): Task unsub(); settleCurrentDetection(); }, 5000); + // The detection window must not keep a one-shot (`pi -p`) process alive: + // without pi-tasks nobody replies, so the full 5s would delay exit. + timer.unref?.(); const unsub = pi.events.on(replyChannel(TASKS_RPC.ping, requestId), (raw: unknown) => { const reply = raw as RpcReply | undefined; if (!reply?.success || !reply.data) return; diff --git a/test/task-rpc.test.ts b/test/task-rpc.test.ts index b647277..ae67355 100644 --- a/test/task-rpc.test.ts +++ b/test/task-rpc.test.ts @@ -53,6 +53,27 @@ describe("task-rpc checkTasksVersion", () => { expect(setTasksAvailable).not.toHaveBeenCalled(); }); + it("unrefs the detection timer (so pi -p can exit)", () => { + const realSetTimeout = global.setTimeout; + const unref = vi.fn(); + const timeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation(((fn: TimerHandler, ms?: number, ...args: any[]) => { + if (ms === 5000) return { unref } as any; + return realSetTimeout(fn, ms, ...args); + }) as typeof setTimeout); + const { pi } = createMockPi(); + const bridge = createTaskRuntimeBridge({ + pi, + isTasksAvailable: () => false, + setTasksAvailable: vi.fn(), + getNativeTaskStore: () => undefined, + }); + + bridge.checkTasksVersion(); + + expect(unref).toHaveBeenCalledTimes(1); + timeoutSpy.mockRestore(); + }); + it("ignores pi-loop's own native ping reply", async () => { const mock = createMockPi(); const setTasksAvailable = vi.fn();