Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34.4k
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:
constcrypto=require("crypto");consthttp=require('http');consta=Buffer.alloc(1e8);functiondoWork(ms){conststart=Date.now();while(Date.now()-start<ms){crypto.randomFillSync(a);}}letsocketId=0;constserver=http.createServer((req,res)=>{if(req.socket.__socketId===undefined){req.socket.__socketId=socketId++;constst=req.socket.setTimeout;req.socket.setTimeout=(a,b)=>{console.log(newDate(),"Setting timeout on socket ",req.socket.__socketId);returnst.call(req.socket,a,b);};req.socket.once("close",()=>console.log(newDate(),`Closing ${req.socket.__socketId}`));}constreqId=req.headers["x-request-id"];console.log(newDate(),`[${reqId}] got request on socket ${req.socket.__socketId}`);doWork(1500);console.log(newDate(),`[${reqId}] sending response`);res.end('hello world\r\n');});server.keepAliveTimeout=1000;// shortened from default 5000 to make test fasterserver.listen(8080,'127.0.0.1');Process 2:
consthttp=require('http');constkeepAliveAgent=newhttp.Agent({keepAlive: true,maxSockets: 2});letsocketId=0;for(leti=0;i<3;i++){constreq=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(newDate(),`[${i}] assigned socket ${socket.__id}`);});req.on('response',function_onResponse(res){res.on('data',function_onData(chunk){});res.on('end',function_doneResponse(){console.log(newDate(),`[${i}] res.end`);});});req.on('error',function_onError(err){console.log(newDate(),`[${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
ECONNRESETs 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.