Commit 53eeed2e90c for nodejs

commit 53eeed2e90cd37d9594338ff53753550ec836db3
Author: Filip Skokan <panva.ip@gmail.com>
Date:   Sat Oct 3 09:35:17 2026 +0200

    test: wait for ZIP source streams to close

    A destroyed read stream can still have a pending open or close. Wait for
    closure before removing the temporary files, so cleanup cannot race a
    pending open and cause ENOENT in test-zlib-zip-files.

    Signed-off-by: Filip Skokan <panva.ip@gmail.com>
    Assisted-by: Codex
    PR-URL: https://github.com/nodejs/node/pull/66429
    Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
    Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>

diff --git a/test/parallel/test-zlib-zip-files.js b/test/parallel/test-zlib-zip-files.js
index 0c4ed6efe88..b25dd874c70 100644
--- a/test/parallel/test-zlib-zip-files.js
+++ b/test/parallel/test-zlib-zip-files.js
@@ -22,23 +22,16 @@ async function collect(readable) {
   return Buffer.concat(chunks);
 }

-// Disposal of a streaming source completes asynchronously - its descriptor is
-// closed on the libuv threadpool - and an abandoned archive tears its queued
-// sources down one after another, each waiting on the previous close. So the
-// moment "every source is destroyed" can trail the destroy() call, arbitrarily
-// far on a loaded machine. Wait for that real end state rather than assuming a
-// fixed delay has been long enough.
-async function waitForAllDestroyed(streams) {
-  const deadline = Date.now() + common.platformTimeout(5000);
-  while (streams.some((s) => !s.destroyed) && Date.now() < deadline) {
-    await new Promise((resolve) => setTimeout(resolve, 5));
-  }
+// destroy() sets .destroyed before a pending open or close finishes. Wait for
+// the sources to close before removing their files.
+async function waitForAllClosed(streams) {
+  await Promise.all(streams.map((stream) =>
+    (stream.closed ? undefined : new Promise((resolve) => stream.once('close', resolve)))));
 }

-// Write `count` throwaway files and return streaming entries backed by their
-// (eagerly opened) read streams, so a test can observe whether each source is
-// destroyed. The worst case for leaks: every descriptor is open before the
-// archive starts.
+// Write `count` throwaway files and return entries backed by read streams,
+// so the test can observe how disposal closes every source. Their asynchronous
+// opens may still be pending when the archive starts.
 async function streamingEntries(count) {
   const dir = await fsp.mkdtemp(path.join(tmpdir.path, 'zlib-zip-dispose-'));
   const streams = [];
@@ -166,7 +159,7 @@ test('an abandoned createZipArchive() destroys the sources of entries it never r
       if (seen > 256 * 1024) break; // Bail while still inside the first member
     }
     archive.destroy();
-    await waitForAllDestroyed(streams);
+    await waitForAllClosed(streams);
     const open = streams.filter((s) => !s.destroyed);
     assert.strictEqual(open.length, 0, `${open.length} source streams left open`);
   } finally {
@@ -196,7 +189,7 @@ test('zipEntry disposal destroys a streaming source, sync and async', async () =
   try {
     entries[0][Symbol.dispose]();
     await entries[1][Symbol.asyncDispose]();
-    await waitForAllDestroyed(streams);
+    await waitForAllClosed(streams);
     assert.strictEqual(streams[0].destroyed, true);
     assert.strictEqual(streams[1].destroyed, true);
   } finally {
@@ -232,7 +225,7 @@ test('createZipArchiveSync() throws on a streaming entry and disposes the rest',
   try {
     assert.throws(() => Array.from(zlib.createZipArchiveSync(entries)),
                   { code: 'ERR_INVALID_STATE' });
-    await waitForAllDestroyed(streams);
+    await waitForAllClosed(streams);
     assert.strictEqual(streams.filter((s) => !s.destroyed).length, 0);
   } finally {
     await fsp.rm(dir, { recursive: true, force: true });