From 3f62e4369656b66913b2c72767597871fe2d4cb1 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Fri, 11 Sep 2026 17:46:22 -0300 Subject: [PATCH] test(compression): assert idle eviction terminates at the resource level (#13371) Merged as the credit vehicle for #12542. Reverse-TDD verified on the tip: 8/8 with the fix, 7/8 with `terminate()` disabled. --- .../12542-compression-idle-terminate-test.md | 1 + .../compression/compression-worker.test.ts | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 changelog.d/maintenance/12542-compression-idle-terminate-test.md diff --git a/changelog.d/maintenance/12542-compression-idle-terminate-test.md b/changelog.d/maintenance/12542-compression-idle-terminate-test.md new file mode 100644 index 0000000000..744e38f854 --- /dev/null +++ b/changelog.d/maintenance/12542-compression-idle-terminate-test.md @@ -0,0 +1 @@ +- **test(compression):** cover idle worker eviction at the resource level — the pool must call `terminate()` and must not retain the worker's `MessagePort`, complementing the `exit`-event assertion added with the fix diff --git a/tests/unit/compression/compression-worker.test.ts b/tests/unit/compression/compression-worker.test.ts index 0ca4cbd453..93265c91e7 100644 --- a/tests/unit/compression/compression-worker.test.ts +++ b/tests/unit/compression/compression-worker.test.ts @@ -1,5 +1,6 @@ import assert from "node:assert/strict"; import { after, describe, it } from "node:test"; +import { Worker } from "node:worker_threads"; import { isCompressionWorkerEligible, isStrictlySerializable, @@ -136,6 +137,40 @@ describe("compression worker execution", () => { } }); + it("terminates an idle worker instead of only dropping it from the pool", async () => { + const spawned = new Set(); + const terminated: Promise[] = []; + const originalPostMessage = Worker.prototype.postMessage; + const originalTerminate = Worker.prototype.terminate; + Worker.prototype.postMessage = function (this: Worker, ...args) { + spawned.add(this); + return originalPostMessage.apply(this, args); + }; + Worker.prototype.terminate = function (this: Worker) { + const exit = originalTerminate.call(this); + terminated.push(exit); + return exit; + }; + const messagePorts = () => + process.getActiveResourcesInfo().filter((resource) => resource === "MessagePort").length; + const portsBefore = messagePorts(); + const pool = new CompressionWorkerPool({ size: 1, idleMs: 50 }); + try { + await pool.run(body, "stacked", { config }); + await new Promise((resolve) => setTimeout(resolve, 300)); + assert.equal(spawned.size, 1); + assert.equal(terminated.length, 1, "idle eviction must terminate the worker thread"); + await Promise.all(terminated); + assert.ok(messagePorts() <= portsBefore, "idle eviction must not retain the worker's port"); + } finally { + Worker.prototype.postMessage = originalPostMessage; + Worker.prototype.terminate = originalTerminate; + await pool.close(); + // Reap anything the pool forgot so a regression fails instead of hanging the runner. + await Promise.all([...spawned].map((worker) => worker.terminate().catch(() => undefined))); + } + }); + it("keeps the parent event loop responsive while two workers overlap", async () => { const largeBody = { messages: Array.from({ length: 400 }, (_, index) => ({