Skip to content

Commit 596940c

Browse files
jazellyzanettea
authored andcommitted
http: reduce likelihood of race conditions on keep-alive timeout
Fixes: #52649 Refs: #54293 Co-authored-by: Arrigo Zanette <zanettea@gmail.com> PR-URL: #54863 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: LiviaMedeiros <livia@cirno.name> Reviewed-By: Jake Yuesong Li <jake.yuesong@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
1 parent d288ec3 commit 596940c

3 files changed

Lines changed: 57 additions & 5 deletions

File tree

‎lib/_http_agent.js‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ const {
4949
constkOnKeylog=Symbol('onkeylog');
5050
constkRequestOptions=Symbol('requestOptions');
5151
constkRequestAsyncResource=Symbol('requestAsyncResource');
52+
53+
// TODO(jazelly): make this configurable
54+
constHTTP_AGENT_KEEP_ALIVE_TIMEOUT_BUFFER=1000;
5255
// New Agent code.
5356

5457
// The largest departure from the previous implementation is that
@@ -473,6 +476,7 @@ Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
473476
socket.unref();
474477

475478
letagentTimeout=this.options.timeout||0;
479+
letcanKeepSocketAlive=true;
476480

477481
if(socket._httpMessage?.res){
478482
constkeepAliveHint=socket._httpMessage.res.headers['keep-alive'];
@@ -481,9 +485,15 @@ Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
481485
consthint=/^timeout=(\d+)/.exec(keepAliveHint)?.[1];
482486

483487
if(hint){
484-
constserverHintTimeout=NumberParseInt(hint)*1000;
485-
486-
if(serverHintTimeout<agentTimeout){
488+
// Let the timer expire before the announced timeout to reduce
489+
// the likelihood of ECONNRESET errors
490+
letserverHintTimeout=(NumberParseInt(hint)*1000)-HTTP_AGENT_KEEP_ALIVE_TIMEOUT_BUFFER;
491+
serverHintTimeout=serverHintTimeout>0 ? serverHintTimeout : 0;
492+
if(serverHintTimeout===0){
493+
// Cannot safely reuse the socket because the server timeout is
494+
// too short
495+
canKeepSocketAlive=false;
496+
}elseif(serverHintTimeout<agentTimeout){
487497
agentTimeout=serverHintTimeout;
488498
}
489499
}
@@ -494,7 +504,7 @@ Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
494504
socket.setTimeout(agentTimeout);
495505
}
496506

497-
returntrue;
507+
returncanKeepSocketAlive;
498508
};
499509

500510
Agent.prototype.reuseSocket=functionreuseSocket(socket,req){

‎lib/_http_server.js‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ const kConnections = Symbol('http.server.connections');
182182
constkConnectionsCheckingInterval=Symbol('http.server.connectionsCheckingInterval');
183183

184184
constHTTP_SERVER_TRACE_EVENT_NAME='http.server.request';
185+
// TODO(jazelly): make this configurable
186+
constHTTP_SERVER_KEEP_ALIVE_TIMEOUT_BUFFER=1000;
185187

186188
classHTTPServerAsyncResource{
187189
constructor(type,socket){
@@ -998,7 +1000,9 @@ function resOnFinish(req, res, socket, state, server) {
9981000
}
9991001
}elseif(state.outgoing.length===0){
10001002
if(server.keepAliveTimeout&&typeofsocket.setTimeout==='function'){
1001-
socket.setTimeout(server.keepAliveTimeout);
1003+
// Increase the internal timeout wrt the advertised value to reduce
1004+
// the likelihood of ECONNRESET errors.
1005+
socket.setTimeout(server.keepAliveTimeout+HTTP_SERVER_KEEP_ALIVE_TIMEOUT_BUFFER);
10021006
state.keepAliveTimeoutSet=true;
10031007
}
10041008
}else{
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
consthttp=require('http');
5+
6+
constmakeRequest=(port,agent)=>
7+
newPromise((resolve,reject)=>{
8+
constreq=http.get(
9+
{path: '/', port, agent },
10+
(res)=>{
11+
res.resume();
12+
res.on('end',()=>resolve());
13+
},
14+
);
15+
req.on('error',(e)=>reject(e));
16+
req.end();
17+
});
18+
19+
constserver=http.createServer(
20+
{keepAliveTimeout: common.platformTimeout(2000),keepAlive: true},
21+
common.mustCall((req,res)=>{
22+
constbody='hello world\n';
23+
res.writeHead(200,{'Content-Length': body.length});
24+
res.write(body);
25+
res.end();
26+
},2)
27+
);
28+
29+
constagent=newhttp.Agent({maxSockets: 5,keepAlive: true});
30+
31+
server.listen(0,common.mustCall(asyncfunction(){
32+
awaitmakeRequest(this.address().port,agent);
33+
// Block the event loop for 2 seconds
34+
Atomics.wait(newInt32Array(newSharedArrayBuffer(4)),0,0,2000);
35+
awaitmakeRequest(this.address().port,agent);
36+
server.close();
37+
agent.destroy();
38+
}));

0 commit comments

Comments
 (0)