diff --git a/src/js/node/net.ts b/src/js/node/net.ts index 72f86c37296c6..f8c770f020e63 100644 --- a/src/js/node/net.ts +++ b/src/js/node/net.ts @@ -76,9 +76,6 @@ const bunTLSConnectOptions = Symbol.for("::buntlsconnectoptions::"); const kRealListen = Symbol("kRealListen"); -function closeNT(self) { - self.emit("close"); -} function endNT(socket, callback, err) { socket.end(); callback(err); @@ -642,6 +639,14 @@ const Socket = (function (InternalSocket) { } _destroy(err, callback) { + const socket = this[bunSocketInternal]; + if (socket) { + this[bunSocketInternal] = null; + // we still have a socket, call end before destroy + process.nextTick(endNT, socket, callback, err); + return; + } + // no socket, just destroy process.nextTick(closeNT, callback, err); } @@ -655,6 +660,7 @@ const Socket = (function (InternalSocket) { this.#final_callback = callback; } else { // emit FIN not allowing half open + this[bunSocketInternal] = null; process.nextTick(endNT, socket, callback); } } diff --git a/test/js/node/net/node-destroy-fixture.js b/test/js/node/net/node-destroy-fixture.js new file mode 100644 index 0000000000000..b380a543f3900 --- /dev/null +++ b/test/js/node/net/node-destroy-fixture.js @@ -0,0 +1,14 @@ +const net = require("node:net"); +const { promise, resolve } = Promise.withResolvers(); +const client = net.createConnection(process.env.PORT, "localhost"); +client.on("connect", () => { + client.destroy(); + resolve(0); +}); + +client.on("error", err => { + console.error("error", err); + resolve(1); +}); + +await promise; diff --git a/test/js/node/net/node-net.test.ts b/test/js/node/net/node-net.test.ts index 3c5617d3f64a7..f7cf81dcc35cf 100644 --- a/test/js/node/net/node-net.test.ts +++ b/test/js/node/net/node-net.test.ts @@ -523,3 +523,35 @@ it("should not hang after FIN", async () => { server.close(); } }); + +it("should not hang after destroy", async () => { + const net = require("node:net"); + const { promise: listening, resolve: resolveListening, reject } = Promise.withResolvers(); + const server = net.createServer(c => { + c.write("Hello client"); + }); + try { + server.on("error", reject); + server.listen(0, () => { + resolveListening(server.address().port); + }); + const process = Bun.spawn({ + cmd: [bunExe(), join(import.meta.dir, "node-destroy-fixture.js")], + stderr: "inherit", + stdin: "ignore", + stdout: "inherit", + env: { + ...bunEnv, + PORT: ((await listening) as number).toString(), + }, + }); + const timeout = setTimeout(() => { + process.kill(); + reject(new Error("Timeout")); + }, 1000); + expect(await process.exited).toBe(0); + clearTimeout(timeout); + } finally { + server.close(); + } +});