Блуждающий ECONNRESET



Достаточно частый кейс в node.js-разработке, когда мы видим в логах блуждающую ошибку ECONNRESET при запросах в другой бэкенд. Начинаем искать, идём на вторую машинку, а там тишина, ошибок в логах нет. Как обычно отвечают — у нас всё хорошо, проблемы на вашей стороне.



Одна из причин это включенный keepalive. Идея keepalive в том, что мы можем переиспользовать коннекшены, не создавать новый коннекшн на каждый запрос. Клиент стучится на сервер, открывает коннекшн, удерживает его и при следующем заходе обращается к уже созданному коннекшену. А что произойдёт, если клиент удерживает коннекшн дольше, чем сервер? Т.е. сервер имеет например keepalive_timeout 4, а клиент создал http.Agent с timeout: 5? Мы и получим тот самый ECONNRESET.



И даже при равных значениях можно достаточно легко войти в состояние гонки:





import http from 'node:http';



const server = http.createServer((req, res) => { res.end('some stuff') });



server.keepAliveTimeout = 5000; //default



server.listen(1337, '127.0.0.1', startSendingRequests);



function startSendingRequests() {

const keepAliveAgent = new http.Agent({keepAlive: true, timeout: 5000});



setInterval(() => {

http.get('http://127.0.0.1:1337', {agent: keepAliveAgent}, (res) => {

res.on('data', (chunk) => {

console.log(`BODY: ${chunk}`);

});

}).on('error', (e) => {

console.error(`problem with request: ${e.message}`);

});

}, 5000)

}





Как решить проблему? Во-первых, если это возжно нужно покрутить таймауты. На клиенте таймаут должен быть меньше чем на сервере. Это даст возможность клиенту закрыть соединение самостоятельно, не дожидаясь ошибки.



Во-вторых, документация node.js предлагает такой паттерн ретрая:





.on('error', (err) => {

// Check if retry is needed

if (req.reusedSocket && err.code === 'ECONNRESET') {

retriableRequest();

}

});




UPD



И конечно важно помнить, что причины, по которым коннекшен может быть закрыт со стороны сервера — разные. Таймаут keepalive лишь один из случаев.