net
socket does not behave according to stream documentation after close #53512
Description
Version
v18.20.2
Platform
Linux desktop-home 6.6.33 #1-NixOS SMP PREEMPT_DYNAMIC Wed Jun 12 09:13:03 UTC 2024 x86_64 GNU/Linux
Subsystem
net
What steps will reproduce the bug?
"use strict";
const net = require("net");
let server = net.createServer((socket) => {
socket.on("close", () => {
console.log("socket fully closed");
});
let success = socket.write("test");
console.log({ success }); // { success: true }
setTimeout(() => {
// This *should* throw an error, according to streams documentation
let success = socket.write("test");
console.log({ success }); // instead, it logs { success: false }
}, 2000);
});
server.listen(4891);
let client = net.connect(4891, () => {
client.end();
});
client.on("data", (data) => {
console.log("recv:", data);
});
/* Full output:
{ success: true }
recv: <Buffer 74 65 73 74>
socket fully closed
{ success: false }
*/
How often does it reproduce? Is there a required condition?
Consistently happens.
What is the expected behavior? Why is that the expected behavior?
After a socket has been closed by the other side, attempting to write to it should result in a write error; a net.Socket
is a stream.Writable
, which says the following in the documentation:
Calling the stream.write() method after calling stream.end() will raise an error.
Although in the most literal interpretation this only specifies that an error would occur if end
was called on the same side that you are attempting to write from, it is surprising behaviour that this is different when the socket is closed from the other side, because the writable stream has still ended all the same (and the above reduced testcase demonstrates this by logging the 'socket fully closed' event).
What do you see instead?
Instead, the write-while-closed merely returns false
as if the internal buffer has temporarily filled up, but then never actually emits a drain
event (presumably because there is no socket to drain to anymore).
Additional information
I worked this out after several hours of debugging, and I was unable to find any reference to this behaviour in the documentation; in my case, it created a difficult-to-debug silent failure, as my code was waiting for the drain
signal to continue writing data, but never received it.
Activity