Compare commits

...

2 Commits

Author SHA1 Message Date
Claude Bot
69df33c1ca fix tests: remove race conditions and use test runner timeout
- Move assertions out of server.listen callback into main async flow
- Await server listening via Promise before making requests
- Replace setTimeout/Promise.race with bun test timeout parameter
- Proper try/finally for server cleanup

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-20 02:36:41 +00:00
Claude Bot
0cae148e65 fix(node:http): emit close event on ServerResponse when socket closes
The `#onClose()` method in `NodeHTTPServerSocket` was missing a call to
`callCloseCallback(this)`, so the ServerResponse's "close" event was
never emitted when the underlying socket closed (e.g. client disconnect).
This matches Node.js behavior where both `req` and `res` emit "close".

Fixes #14697

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-20 02:24:57 +00:00
2 changed files with 78 additions and 0 deletions

View File

@@ -904,6 +904,11 @@ const NodeHTTPServerSocket = class Socket extends Duplex {
req.destroy();
}
}
// Emit "close" on the ServerResponse (httpMessage) via the close callback
// set in assignSocketInternal. This ensures the response's "close" event
// fires when the underlying socket is closed (e.g. client disconnect).
callCloseCallback(this);
}
#onCloseForDestroy(closeCallback) {
this.#onClose();

View File

@@ -0,0 +1,73 @@
import { expect, test } from "bun:test";
import { createServer } from "node:http";
test("ServerResponse emits close event on client disconnect", async () => {
const { promise, resolve } = Promise.withResolvers<void>();
let requestClosed = false;
let responseClosed = false;
const server = createServer((req, res) => {
req.once("close", () => {
requestClosed = true;
});
res.once("close", () => {
responseClosed = true;
resolve();
});
// Don't end the response — wait for the client to disconnect.
});
try {
// Await server listening before making requests
const port = await new Promise<number>(res => {
server.listen(0, () => res(server.address()!.port));
});
// Connect and immediately abort to simulate client disconnect
const controller = new AbortController();
fetch(`http://localhost:${port}`, { signal: controller.signal }).catch(() => {});
// Give the server a moment to receive the request before aborting
await Bun.sleep(50);
controller.abort();
// Wait for the close event on the response
await promise;
expect(requestClosed).toBe(true);
expect(responseClosed).toBe(true);
} finally {
server.close();
}
}, 5000);
test("ServerResponse emits close event on normal response end", async () => {
const { promise, resolve } = Promise.withResolvers<void>();
let responseClosed = false;
const server = createServer((req, res) => {
res.once("close", () => {
responseClosed = true;
resolve();
});
res.end("hello");
});
try {
// Await server listening before making requests
const port = await new Promise<number>(res => {
server.listen(0, () => res(server.address()!.port));
});
const resp = await fetch(`http://localhost:${port}`);
const text = await resp.text();
expect(text).toBe("hello");
await promise;
expect(responseClosed).toBe(true);
} finally {
server.close();
}
}, 5000);