allowHalfOpen
option from net
has no effect due to autoDestroy = true
#49073
Open
Description
Version
v20.5.0 and v18.17.0 and more
Platform
Linux futpib-desktop 6.1.39-3-lts #1 SMP PREEMPT_DYNAMIC Wed, 02 Aug 2023 10:12:55 +0000 x86_64 GNU/Linux
Subsystem
net
What steps will reproduce the bug?
// net-allowHalfOpen.js
const net = require('net');
async function main() {
let resolveServerSocket;
const serverSocketPromise = new Promise((resolve, reject) => {
resolveServerSocket = resolve;
});
const server = net.createServer({
allowHalfOpen: true,
}, (socket) => {
resolveServerSocket(socket);
}).listen();
const clientSocket = await new Promise(resolve => {
const socket = net.createConnection({
allowHalfOpen: true,
port: server.address().port,
host: server.address().address,
}, () => {
resolve(socket);
});
});
const serverSocket = await serverSocketPromise;
await new Promise((resolve, reject) => {
clientSocket.write('data written to client socket', (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
await new Promise(resolve => {
clientSocket.end(resolve);
});
for await (const chunk of serverSocket) {
console.log('read from server socket:', chunk.toString());
}
console.log('server socket ended');
if (serverSocket.destroyed) {
console.error('server socket is already destroyed 😿');
}
await new Promise((resolve, reject) => {
serverSocket.write('data written to server socket', (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
await new Promise(resolve => {
serverSocket.end(resolve);
});
for await (const chunk of clientSocket) {
console.log('read from client socket:', chunk.toString());
}
console.log('client socket ended');
server.close();
}
main();
$ node net-allowHalfOpen.js
read from server socket: data written to client socket
server socket ended
server socket is already destroyed 😿
node:internal/errors:496
ErrorCaptureStackTrace(err);
^
Error [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed
at new NodeError (node:internal/errors:405:5)
at _write (node:internal/streams/writable:331:11)
at Writable.write (node:internal/streams/writable:344:10)
at /home/futpib/code/tmp/net-allowHalfOpen.js:52:22
at new Promise (<anonymous>)
at main (/home/futpib/code/tmp/net-allowHalfOpen.js:51:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
code: 'ERR_STREAM_DESTROYED'
}
Node.js v20.5.0
How often does it reproduce? Is there a required condition?
Reproduces every time.
What is the expected behavior? Why is that the expected behavior?
Patch the example above with this workaround/hack to get what I consider to be expected behaviour:
--- net-allowHalfOpen.js 2023-08-09 00:52:47.771904675 +0400
+++ net-allowHalfOpen-hack.js 2023-08-09 00:52:42.848526177 +0400
@@ -24,6 +24,11 @@
const serverSocket = await serverSocketPromise;
+ clientSocket._writableState.autoDestroy = false;
+ clientSocket._readableState.autoDestroy = false;
+ serverSocket._writableState.autoDestroy = false;
+ serverSocket._readableState.autoDestroy = false;
+
await new Promise((resolve, reject) => {
clientSocket.write('data written to client socket', (error) => {
if (error) {
Full file after the hack
// net-allowHalfOpen-hack.js
const net = require('net');
async function main() {
let resolveServerSocket;
const serverSocketPromise = new Promise((resolve, reject) => {
resolveServerSocket = resolve;
});
const server = net.createServer({
allowHalfOpen: true,
}, (socket) => {
resolveServerSocket(socket);
}).listen();
const clientSocket = await new Promise(resolve => {
const socket = net.createConnection({
allowHalfOpen: true,
port: server.address().port,
host: server.address().address,
}, () => {
resolve(socket);
});
});
const serverSocket = await serverSocketPromise;
clientSocket._writableState.autoDestroy = false;
clientSocket._readableState.autoDestroy = false;
serverSocket._writableState.autoDestroy = false;
serverSocket._readableState.autoDestroy = false;
await new Promise((resolve, reject) => {
clientSocket.write('data written to client socket', (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
await new Promise(resolve => {
clientSocket.end(resolve);
});
for await (const chunk of serverSocket) {
console.log('read from server socket:', chunk.toString());
}
console.log('server socket ended');
if (serverSocket.destroyed) {
console.error('server socket is already destroyed 😿');
}
await new Promise((resolve, reject) => {
serverSocket.write('data written to server socket', (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
await new Promise(resolve => {
serverSocket.end(resolve);
});
for await (const chunk of clientSocket) {
console.log('read from client socket:', chunk.toString());
}
console.log('client socket ended');
server.close();
}
main();
$ node net-allowHalfOpen-hack.js
read from server socket: data written to client socket
server socket ended
read from client socket: data written to server socket
client socket ended
This is the expected behavior because this allows for half-open sockets to actually be used, otherwise there is no point in having a allowHalfOpen
option, it just does not work. More precisely, the socket is closed (destroyed) when only one of it's directions has ended.
What do you see instead?
The server socket is closed regardless of allowHalfOpen
option due to autoDestroy
option hard set to true
.
Line 405 in 6432060
$ node net-allowHalfOpen.js
read from server socket: data written to client socket
server socket ended
server socket is already destroyed 😿
node:internal/errors:496
ErrorCaptureStackTrace(err);
^
Error [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed
at new NodeError (node:internal/errors:405:5)
at _write (node:internal/streams/writable:331:11)
at Writable.write (node:internal/streams/writable:344:10)
at /home/futpib/code/tmp/net-allowHalfOpen.js:52:22
at new Promise (<anonymous>)
at main (/home/futpib/code/tmp/net-allowHalfOpen.js:51:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
code: 'ERR_STREAM_DESTROYED'
}
Node.js v20.5.0
Additional information
No response
Activity