http: add Agent.agentKeepAliveTimeoutBuffer option
Some checks failed
Coverage Windows / coverage-windows (push) Waiting to run
Coverage Linux (without intl) / coverage-linux-without-intl (push) Failing after 1m45s
Coverage Linux / coverage-linux (push) Failing after 1m1s
Test and upload documentation to artifacts / build-docs (push) Failing after 6m37s
Linters / lint-addon-docs (push) Successful in 2m28s
Linters / lint-cpp (push) Successful in 4m20s
Linters / format-cpp (push) Has been skipped
Linters / lint-py (push) Successful in 2m39s
Linters / lint-yaml (push) Successful in 2m32s
Linters / lint-sh (push) Failing after 1m41s
Linters / lint-codeowners (push) Failing after 56s
Linters / lint-pr-url (push) Has been skipped
Linters / lint-readme (push) Successful in 1m24s
Notify on Push / Notify on Force Push on `main` (push) Has been skipped
Linters / lint-js-and-md (push) Successful in 14m58s
Notify on Push / Notify on Push on `main` that lacks metadata (push) Has been skipped
Scorecard supply-chain security / Scorecard analysis (push) Failing after 52s

PR-URL: https://github.com/nodejs/node/pull/59315
Reviewed-By: Jason Zhang <xzha4350@gmail.com>
This commit is contained in:
Haram Jeong 2025-08-13 11:32:30 +09:00 committed by GitHub
parent bb6e8351c7
commit a4b4eca94c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 64 additions and 3 deletions

View file

@ -116,6 +116,10 @@ http.get({
<!-- YAML <!-- YAML
added: v0.3.4 added: v0.3.4
changes: changes:
- version:
- REPLACEME
pr-url: https://github.com/nodejs/node/pull/59315
description: Add support for `agentKeepAliveTimeoutBuffer`.
- version: - version:
- v24.5.0 - v24.5.0
pr-url: https://github.com/nodejs/node/pull/58980 pr-url: https://github.com/nodejs/node/pull/58980
@ -156,6 +160,12 @@ changes:
the [initial delay][] the [initial delay][]
for TCP Keep-Alive packets. Ignored when the for TCP Keep-Alive packets. Ignored when the
`keepAlive` option is `false` or `undefined`. **Default:** `1000`. `keepAlive` option is `false` or `undefined`. **Default:** `1000`.
* `agentKeepAliveTimeoutBuffer` {number} Milliseconds to subtract from
the server-provided `keep-alive: timeout=...` hint when determining socket
expiration time. This buffer helps ensure the agent closes the socket
slightly before the server does, reducing the chance of sending a request
on a socket thats about to be closed by the server.
**Default:** `1000`.
* `maxSockets` {number} Maximum number of sockets to allow per host. * `maxSockets` {number} Maximum number of sockets to allow per host.
If the same host opens multiple concurrent connections, each request If the same host opens multiple concurrent connections, each request
will use new socket until the `maxSockets` value is reached. will use new socket until the `maxSockets` value is reached.

View file

@ -22,6 +22,7 @@
'use strict'; 'use strict';
const { const {
NumberIsFinite,
NumberParseInt, NumberParseInt,
ObjectKeys, ObjectKeys,
ObjectSetPrototypeOf, ObjectSetPrototypeOf,
@ -60,8 +61,6 @@ const kOnKeylog = Symbol('onkeylog');
const kRequestOptions = Symbol('requestOptions'); const kRequestOptions = Symbol('requestOptions');
const kRequestAsyncResource = Symbol('requestAsyncResource'); const kRequestAsyncResource = Symbol('requestAsyncResource');
// TODO(jazelly): make this configurable
const HTTP_AGENT_KEEP_ALIVE_TIMEOUT_BUFFER = 1000;
// New Agent code. // New Agent code.
// The largest departure from the previous implementation is that // The largest departure from the previous implementation is that
@ -114,6 +113,14 @@ function Agent(options) {
this.scheduling = this.options.scheduling || 'lifo'; this.scheduling = this.options.scheduling || 'lifo';
this.maxTotalSockets = this.options.maxTotalSockets; this.maxTotalSockets = this.options.maxTotalSockets;
this.totalSocketCount = 0; this.totalSocketCount = 0;
this.agentKeepAliveTimeoutBuffer =
typeof this.options.agentKeepAliveTimeoutBuffer === 'number' &&
this.options.agentKeepAliveTimeoutBuffer >= 0 &&
NumberIsFinite(this.options.agentKeepAliveTimeoutBuffer) ?
this.options.agentKeepAliveTimeoutBuffer :
1000;
const proxyEnv = this.options.proxyEnv; const proxyEnv = this.options.proxyEnv;
if (typeof proxyEnv === 'object' && proxyEnv !== null) { if (typeof proxyEnv === 'object' && proxyEnv !== null) {
this[kProxyConfig] = parseProxyConfigFromEnv(proxyEnv, this.protocol, this.keepAlive); this[kProxyConfig] = parseProxyConfigFromEnv(proxyEnv, this.protocol, this.keepAlive);
@ -559,7 +566,7 @@ Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
if (hint) { if (hint) {
// Let the timer expire before the announced timeout to reduce // Let the timer expire before the announced timeout to reduce
// the likelihood of ECONNRESET errors // the likelihood of ECONNRESET errors
let serverHintTimeout = (NumberParseInt(hint) * 1000) - HTTP_AGENT_KEEP_ALIVE_TIMEOUT_BUFFER; let serverHintTimeout = (NumberParseInt(hint) * 1000) - this.agentKeepAliveTimeoutBuffer;
serverHintTimeout = serverHintTimeout > 0 ? serverHintTimeout : 0; serverHintTimeout = serverHintTimeout > 0 ? serverHintTimeout : 0;
if (serverHintTimeout === 0) { if (serverHintTimeout === 0) {
// Cannot safely reuse the socket because the server timeout is // Cannot safely reuse the socket because the server timeout is

View file

@ -0,0 +1,44 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const http = require('http');
// Ensure agentKeepAliveTimeoutBuffer option sets the correct value or falls back to default.
{
const agent1 = new http.Agent({ agentKeepAliveTimeoutBuffer: 1500, keepAlive: true });
assert.strictEqual(agent1.agentKeepAliveTimeoutBuffer, 1500);
const agent2 = new http.Agent({ agentKeepAliveTimeoutBuffer: -100, keepAlive: true });
assert.strictEqual(agent2.agentKeepAliveTimeoutBuffer, 1000);
const agent3 = new http.Agent({ agentKeepAliveTimeoutBuffer: Infinity, keepAlive: true });
assert.strictEqual(agent3.agentKeepAliveTimeoutBuffer, 1000);
const agent4 = new http.Agent({ keepAlive: true });
assert.strictEqual(agent4.agentKeepAliveTimeoutBuffer, 1000);
}
// Integration test with server sending Keep-Alive timeout header.
{
const SERVER_TIMEOUT = 3;
const BUFFER = 1500;
const server = http.createServer((req, res) => {
res.setHeader('Keep-Alive', `timeout=${SERVER_TIMEOUT}`);
res.end('ok');
});
server.listen(0, common.mustCall(() => {
const agent = new http.Agent({ agentKeepAliveTimeoutBuffer: BUFFER, keepAlive: true });
assert.strictEqual(agent.agentKeepAliveTimeoutBuffer, BUFFER);
http.get({ port: server.address().port, agent }, (res) => {
res.resume();
res.on('end', () => {
agent.destroy();
server.close();
});
});
}));
}