-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
Open
Labels
httpIssues or PRs related to the http subsystem.Issues or PRs related to the http subsystem.
Description
Version
14.19.1 and others
Platform
Linux qa01-api-1 5.13.0-1024-gcp #29~20.04.1-Ubuntu SMP Thu Apr 14 23:15:00 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
Subsystem
net
What steps will reproduce the bug?
Process 1:
const crypto = require("crypto");
const http = require('http');
const a = Buffer.alloc(1e8);
function doWork(ms) {
const start = Date.now();
while (Date.now() - start < ms) {
crypto.randomFillSync(a);
}
}
let socketId = 0;
const server = http.createServer((req, res) => {
if (req.socket.__socketId === undefined) {
req.socket.__socketId = socketId++;
const st = req.socket.setTimeout;
req.socket.setTimeout = (a, b) => {
console.log(new Date(), "Setting timeout on socket ", req.socket.__socketId);
return st.call(req.socket, a, b);
};
req.socket.once("close", () => console.log(new Date(), `Closing ${req.socket.__socketId}`));
}
const reqId = req.headers["x-request-id"];
console.log(new Date(), `[${reqId}] got request on socket ${req.socket.__socketId}`);
doWork(1500);
console.log(new Date(), `[${reqId}] sending response`);
res.end('hello world\r\n');
});
server.keepAliveTimeout = 1000; // shortened from default 5000 to make test faster
server.listen(8080, '127.0.0.1');
Process 2:
const http = require('http');
const keepAliveAgent = new http.Agent({
keepAlive: true,
maxSockets: 2
});
let socketId = 0;
for (let i = 0; i < 3; i++) {
const req = http.request({
agent: keepAliveAgent,
host: '127.0.0.1',
port: 8080,
method: 'GET',
headers: {
"X-Request-Id": `${i}`
}
});
req.end();
req.once("socket", socket => {
if (socket.__id === undefined) socket.__id = socketId++;
console.log(new Date(), `[${i}] assigned socket ${socket.__id}`);
});
req.on('response', function _onResponse(res) {
res.on('data', function _onData(chunk) {});
res.on('end', function _doneResponse() {
console.log(new Date(), `[${i}] res.end`);
});
});
req.on('error', function _onError(err) {
console.log(new Date(), `[${i}] req.error`, err);
});
}
How often does it reproduce? Is there a required condition?
100%
What is the expected behavior?
All three requests should succeed.
What do you see instead?
The server logs will look like this:
2022-06-16T22:37:50.307Z [0] got request on socket 0
2022-06-16T22:37:51.809Z [0] sending response
2022-06-16T22:37:51.813Z Setting timeout on socket 0
2022-06-16T22:37:51.814Z [1] got request on socket 1
2022-06-16T22:37:53.317Z [1] sending response
2022-06-16T22:37:53.318Z Setting timeout on socket 1
2022-06-16T22:37:53.320Z Closing 0
2022-06-16T22:37:53.323Z Closing 1
and client
2022-06-16T22:37:50.303Z [0] assigned socket 0
2022-06-16T22:37:50.305Z [1] assigned socket 1
2022-06-16T22:37:51.814Z [0] res.end
2022-06-16T22:37:51.815Z [2] assigned socket 0
2022-06-16T22:37:53.318Z [1] res.end
2022-06-16T22:37:53.321Z [2] req.error Error: read ECONNRESET
at TCP.onStreamRead (internal/stream_base_commons.js:209:20) {
errno: -104,
code: 'ECONNRESET',
syscall: 'read'
}
Notice: req[2] is assigned a socket at 51.815, just after req[1] is received by the server. Server socket[0] is closed at 53.320, just after the blocking work on req[2], even though there's a request that could be assigned to that socket. I think what's happening is that timers are checked before pending incoming messages are assigned sockets.
Additional information
ECONNRESET
s due to Keep-Alive races need to be handled by clients (see #20256, #38890 and several others), but I think this particular scenario can be fixed to avoid a subset of them.
codinbox
Metadata
Metadata
Assignees
Labels
httpIssues or PRs related to the http subsystem.Issues or PRs related to the http subsystem.