From e6c259b6f8e535012441d41d3dbebaf00a24a45a Mon Sep 17 00:00:00 2001 From: ivanmilevtues Date: Sat, 24 Oct 2026 00:25:38 +0200 Subject: [PATCH] fix(coding-agent): remove sleep's abort listener once the timer fires Co-Authored-By: Claude Opus 5.5 --- packages/coding-agent/src/utils/sleep.ts | 13 +++++++---- packages/coding-agent/test/sleep.test.ts | 28 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 packages/coding-agent/test/sleep.test.ts diff --git a/packages/coding-agent/src/utils/sleep.ts b/packages/coding-agent/src/utils/sleep.ts index 948f93c47928..ae2690efeaf3 100644 --- a/packages/coding-agent/src/utils/sleep.ts +++ b/packages/coding-agent/src/utils/sleep.ts @@ -1,5 +1,6 @@ /** * Sleep helper that respects abort signal. + * The abort listener is removed once the timer fires, so a long-lived signal does not collect one per call. */ export function sleep(ms: number, signal?: AbortSignal): Promise { return new Promise((resolve, reject) => { @@ -8,11 +9,15 @@ export function sleep(ms: number, signal?: AbortSignal): Promise { return; } - const timeout = setTimeout(resolve, ms); - - signal?.addEventListener("abort", () => { + const onAbort = () => { clearTimeout(timeout); reject(new Error("Aborted")); - }); + }; + const timeout = setTimeout(() => { + signal?.removeEventListener("abort", onAbort); + resolve(); + }, ms); + + signal?.addEventListener("abort", onAbort, { once: true }); }); } diff --git a/packages/coding-agent/test/sleep.test.ts b/packages/coding-agent/test/sleep.test.ts new file mode 100644 index 000000000000..0df501a3bb5d --- /dev/null +++ b/packages/coding-agent/test/sleep.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it, vi } from "vitest"; +import { sleep } from "../src/utils/sleep.ts"; + +describe("sleep", () => { + it("resolves after the delay", async () => { + await expect(sleep(1)).resolves.toBeUndefined(); + }); + + it("rejects at once when the signal is already aborted", async () => { + const controller = new AbortController(); + controller.abort(); + await expect(sleep(1000, controller.signal)).rejects.toThrow("Aborted"); + }); + + it("rejects when aborted mid-wait", async () => { + const controller = new AbortController(); + const waiting = sleep(1000, controller.signal); + controller.abort(); + await expect(waiting).rejects.toThrow("Aborted"); + }); + + it("removes its abort listener once the timer fires", async () => { + const controller = new AbortController(); + const remove = vi.spyOn(controller.signal, "removeEventListener"); + await sleep(1, controller.signal); + expect(remove).toHaveBeenCalledWith("abort", expect.any(Function)); + }); +});