From a49dd83b14207dcfb9a3e9b54bba5f9268d2c103 Mon Sep 17 00:00:00 2001 From: Yi LIU Date: Sat, 14 Feb 2026 01:26:05 +0800 Subject: [PATCH] fix(process): reject pending promises when clearing command lane clearCommandLane() was truncating the queue array without calling resolve/reject on pending entries, causing never-settling promises and memory leaks when upstream callers await enqueueCommandInLane(). Splice entries and reject each before clearing so callers can handle the cancellation gracefully. --- src/process/command-queue.test.ts | 30 ++++++++++++++++++++++++++++++ src/process/command-queue.ts | 5 ++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/process/command-queue.test.ts b/src/process/command-queue.test.ts index d08688347..e9f7a7f54 100644 --- a/src/process/command-queue.test.ts +++ b/src/process/command-queue.test.ts @@ -17,6 +17,7 @@ vi.mock("../logging/diagnostic.js", () => ({ })); import { + clearCommandLane, enqueueCommand, enqueueCommandInLane, getActiveTaskCount, @@ -194,4 +195,33 @@ describe("command queue", () => { resolve2(); await Promise.all([first, second]); }); + + it("clearCommandLane rejects pending promises", async () => { + let resolve1!: () => void; + const blocker = new Promise((r) => { + resolve1 = r; + }); + + // First task blocks the lane. + const first = enqueueCommand(async () => { + await blocker; + return "first"; + }); + + // Second task is queued behind the first. + const second = enqueueCommand(async () => "second"); + + // Give the first task a tick to start. + await new Promise((r) => setTimeout(r, 5)); + + const removed = clearCommandLane(); + expect(removed).toBe(1); // only the queued (not active) entry + + // The queued promise should reject. + await expect(second).rejects.toThrow("Command lane cleared"); + + // Let the active task finish normally. + resolve1(); + await expect(first).resolves.toBe("first"); + }); }); diff --git a/src/process/command-queue.ts b/src/process/command-queue.ts index 598007584..4fbe63add 100644 --- a/src/process/command-queue.ts +++ b/src/process/command-queue.ts @@ -162,7 +162,10 @@ export function clearCommandLane(lane: string = CommandLane.Main) { return 0; } const removed = state.queue.length; - state.queue.length = 0; + const pending = state.queue.splice(0); + for (const entry of pending) { + entry.reject(new Error("Command lane cleared")); + } return removed; }