Open
Description
Version
v18.19.0
Platform
Darwin G34X7RD99C 23.2.0 Darwin Kernel Version 23.2.0: Wed Nov 15 21:53:18 PST 2023; root:xnu-10002.61.3~2/RELEASE_ARM64_T6000 arm64
Subsystem
fetch
What steps will reproduce the bug?
Run the following script:
const http = require('node:http');
const runServerAndFetch = async () => {
const sockets = [];
const server = http.createServer((req, res) => {
res.end();
});
server.on('request', req => {
sockets.push(req.socket);
});
await new Promise(res => {
server.listen(10000, res);
});
await fetch(`http://localhost:10000`);
await new Promise(res => {
setTimeout(() => { // Interestingly, there's no error if this `setTimeout` is removed with its contents inlined.
sockets.forEach(socket => socket.end());
server.close(res);
}, 0);
});
};
const main = async () => {
await runServerAndFetch();
await runServerAndFetch();
};
main();
How often does it reproduce? Is there a required condition?
Consistent reproduce
What is the expected behavior? Why is that the expected behavior?
Ideally, both times the server is started and a fetch(...)
is made, it should receive a 200
happily, and the script should exit without any output.
What do you see instead?
node:internal/deps/undici/undici:11730
Error.captureStackTrace(err, this);
^
TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11730:11)
at async runServerAndFetch (/Users/mhentges/dev/backstage-quickstart/node.js:16:3)
at async main (/Users/mhentges/dev/backstage-quickstart/node.js:28:3) {
cause: SocketError: other side closed
at Socket.onSocketEnd (node:internal/deps/undici/undici:8280:26)
at Socket.emit (node:events:529:35)
at endReadableNT (node:internal/streams/readable:1400:12)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
code: 'UND_ERR_SOCKET',
socket: {
localAddress: '::1',
localPort: 56324,
remoteAddress: undefined,
remotePort: undefined,
remoteFamily: undefined,
timeout: undefined,
bytesWritten: 340,
bytesRead: 122
}
}
}
Node.js v18.19.0
Additional information
The reproduce example I've used is simplified and is not conventionally handling request lifecycles properly.
Importantly, this bug can be reproduced using the library the example reproduce is based on:
const http = require('node:http');
const stoppable = require('stoppable');
const runServerAndFetch = async () => {
const server = http.createServer((req, res) => {
res.end();
});
const stoppableServer = stoppable(server);
await new Promise(res => {
server.listen(10000, res);
});
await fetch(`http://localhost:10000`);
await new Promise(res => {
stoppableServer.stop(res);
});
};
const main = async () => {
await runServerAndFetch();
await runServerAndFetch();
};
main();