Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/js/node/_http_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ function emitRequestCloseNT(self) {
self.emit("close");
}

// Both listen() outcomes are deferred with process.nextTick like Node's
// net.Server. A setTimeout here lands in the bun:test fake timer heap while
// jest.useFakeTimers() is active and never fires on its own.
function emitListeningNextTick(self, hostname, port) {
if ((self.listening = !!self[serverSymbol])) {
// TODO: remove the arguments
Expand All @@ -288,6 +291,10 @@ function emitListeningNextTick(self, hostname, port) {
}
}

function emitListenErrorNextTick(self, err) {
self.emit("error", err);
}

// Node.js only requests a client certificate when `requestCert: true`.
// The uSockets SSL context treats `ca` alone as "verify peer", so without
// these two flags an `https.Server({ ca })` would reject every client that
Expand Down Expand Up @@ -684,7 +691,7 @@ Server.prototype.listen = function () {

server[kRealListen](tls, port, host, socketPath, true, onListen);
} catch (err) {
setTimeout(() => server.emit("error", err), 1);
process.nextTick(emitListenErrorNextTick, server, err);
}

return this;
Expand Down Expand Up @@ -1144,7 +1151,7 @@ Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort
this.once("listening", onListen);
}

setTimeout(emitListeningNextTick, 1, this, this[serverSymbol]?.hostname, this[serverSymbol]?.port);
process.nextTick(emitListeningNextTick, this, this[serverSymbol]?.hostname, this[serverSymbol]?.port);
}
};

Expand Down
23 changes: 12 additions & 11 deletions src/js/node/net.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3848,7 +3848,11 @@ Server.prototype.listen = function listen(port, hostname, onListen) {
);
} catch (err) {
const isUnix = path != null;
setTimeout(emitErrorNextTick, 1, this, formatListenError(err, isUnix ? path : hostname, isUnix ? undefined : port));
process.nextTick(
emitErrorNextTick,
this,
formatListenError(err, isUnix ? path : hostname, isUnix ? undefined : port),
);
}
return this;
};
Expand Down Expand Up @@ -3952,14 +3956,11 @@ Server.prototype[kRealListen] = function (
// Unref the handle if the server was unref'ed prior to listening
if (this._unref) this.unref();

// We must schedule the emitListeningNextTick() only after the next run of
// the event loop's IO queue. Otherwise, the server may not actually be listening
// when the 'listening' event is emitted.
//
// That leads to all sorts of confusion.
//
// process.nextTick() is not sufficient because it will run before the IO queue.
setTimeout(emitListeningNextTick, 1, this);
// Bun.listen has already bound, listened and registered the socket with the
// event loop, so like Node this only needs to be deferred to the next tick.
// A setTimeout here lands in the bun:test fake timer heap while
// jest.useFakeTimers() is active and never fires on its own.
process.nextTick(emitListeningNextTick, this);
};

Server.prototype[EventEmitter.captureRejectionSymbol] = function (err, event, sock) {
Expand Down Expand Up @@ -4144,7 +4145,7 @@ function listenInCluster(
server[kClusterUnixPath] = undefined;
handle[kClusterOwner] = null;
handle.close();
setTimeout(emitErrorNextTick, 1, server, err);
process.nextTick(emitErrorNextTick, server, err);
}
return;
}
Expand All @@ -4168,7 +4169,7 @@ Server.prototype[kClusterFauxListen] = function (handle, backlog, path) {
handle[kClusterOwner] = this;
handle.listen(backlog || 511);
if (this._unref) this.unref();
setTimeout(emitListeningNextTick, 1, this);
process.nextTick(emitListeningNextTick, this);
};

function onClusterConnection(err, clientHandle) {
Expand Down
52 changes: 52 additions & 0 deletions test/js/bun/test/test-timers.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { bunEnv, bunExe } from "harness";
import { once } from "node:events";
import http from "node:http";
import net from "node:net";
import path from "node:path";

test("we can go back in time", () => {
Expand Down Expand Up @@ -114,3 +117,52 @@ test("real timer heap is ticked against the real clock under useFakeTimers", asy
expect(proc.signalCode).toBeNull();
expect(exitCode).toBe(0);
});

describe.each([
["net", () => net.createServer()],
["http", () => http.createServer()],
])("%s.Server#listen() while fake timers are active", (_, createServer) => {
test("emits 'listening' without fake time being advanced", async () => {
jest.useFakeTimers();
try {
const server = createServer();
try {
const listening = once(server, "listening");
server.listen(0, "127.0.0.1");
// The deferred emit must not be a timer, or it would sit in the fake heap.
expect(jest.getTimerCount()).toBe(0);
await listening;
expect(server.listening).toBe(true);
expect(server.address()).toMatchObject({ address: "127.0.0.1", port: expect.any(Number) });
} finally {
server.close();
}
} finally {
jest.useRealTimers();
}
});

test("emits 'error' for a port that is in use without fake time being advanced", async () => {
const holder = createServer();
try {
holder.listen(0, "127.0.0.1");
await once(holder, "listening");
const { port } = holder.address() as net.AddressInfo;

jest.useFakeTimers();
try {
const server = createServer();
const errored = once(server, "error");
server.listen(port, "127.0.0.1");
expect(jest.getTimerCount()).toBe(0);
const [err] = await errored;
expect(err).toMatchObject({ code: "EADDRINUSE" });
expect(server.listening).toBe(false);
} finally {
jest.useRealTimers();
}
} finally {
holder.close();
}
});
});
Loading