Skip to content

net socket does not behave according to stream documentation after close #53512

Open
@joepie91

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    streamIssues and PRs related to the stream subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions