From 37c546ee7153ad66513f0ce11f5d8c7e5de20301 Mon Sep 17 00:00:00 2001 From: Taras Mankovski <74687+taras@users.noreply.github.com> Date: Sun, 9 Aug 2026 07:35:28 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AA=20Keep=20the=20HTTP=20journal=20te?= =?UTF-8?q?st's=20teardown=20off=20the=20fetch=20pool's=20clock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HTTP-backend case binds the test server to an ephemeral port and sends Connection: close on every request. Deno's fetch client can open a spare keep-alive connection that never carries a request, and the node:http polyfill's close() waits for it — closeIdleConnections() only reaps sockets that finished a response, and close() destroys the reaper interval that would have expired it — so stop() waited on the client pool's 90s eviction interval (observed as an exactly-3m0s test) or, when eviction never came, past the verification deadline. A fixed default port also let concurrent suites share one listen port on macOS instead of refusing the second bind. Closes #407 --- packages/core/tests/guarded-journal.test.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/core/tests/guarded-journal.test.ts b/packages/core/tests/guarded-journal.test.ts index 5fb625c8..a22ff9a8 100644 --- a/packages/core/tests/guarded-journal.test.ts +++ b/packages/core/tests/guarded-journal.test.ts @@ -96,6 +96,21 @@ function makeTmpDir(): string { return fs.mkdtempSync(path.join(os.tmpdir(), "xmd-guarded-journal-")); } +/** + * A fetch that asks the server to close the connection after each response. + * The test owns the server's lifetime, and stop() waits for every open + * connection — a pooled keep-alive connection held by the runtime's fetch + * client keeps teardown waiting on the client pool's eviction clock. + */ +function closingFetch( + input: Parameters[0], + init?: RequestInit, +): Promise { + const headers = new Headers(init?.headers); + headers.set("connection", "close"); + return globalThis.fetch(input, { ...init, headers }); +} + /** * A file backend shaped like the CLI's FileStream: it appends the shared * NDJSON record and answers readAll() from the events it accepted, so a test @@ -226,7 +241,10 @@ describe("a guarded journal", () => { }); it("keeps the rejected event out of an HTTP backend", function* () { - const server = new DurableStreamTestServer(); + // port 0: the corpus runs under three runtimes concurrently, and a fixed + // port lets those servers collide — macOS shares the listen port between + // processes instead of refusing the second bind. + const server = new DurableStreamTestServer({ port: 0 }); const baseUrl = yield* until(server.start()); yield* ensure(() => until(server.stop())); @@ -238,6 +256,7 @@ describe("a guarded journal", () => { streamId: "guarded-journal", producerId: "guarded-journal-test", epoch: 1, + fetch: closingFetch, }); const stream = guardDurableStream(backend, rejecting([], isFirstExec));