Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit cce4645

Browse filesBrowse files
delvedorMylesBorins
authored andcommitted
http: added scheduling option to http agent
In some cases, it is preferable to use a lifo scheduling strategy for the free sockets instead of default one, which is fifo. This commit introduces a scheduling option to add the ability to choose which strategy best fits your needs. Backport-PR-URL: #35649 PR-URL: #33278 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent d477e2e commit cce4645
Copy full SHA for cce4645

File tree

Expand file treeCollapse file tree

3 files changed

+174
-1
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

3 files changed

+174
-1
lines changed
Open diff view settings
Collapse file

‎doc/api/http.md‎

Copy file name to clipboardExpand all lines: doc/api/http.md
+16Lines changed: 16 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ changes:
116116
- version: v12.19.0
117117
pr-url: https://github.com/nodejs/node/pull/33617
118118
description: Add `maxTotalSockets` option to agent constructor.
119+
- version: REPLACEME
120+
pr-url: https://github.com/nodejs/node/pull/33278
121+
description: Add `scheduling` option to specify the free socket
122+
scheduling strategy.
119123
-->
120124

121125
* `options` {Object} Set of configurable options to set on the agent.
@@ -142,6 +146,18 @@ changes:
142146
* `maxFreeSockets` {number} Maximum number of sockets to leave open
143147
in a free state. Only relevant if `keepAlive` is set to `true`.
144148
**Default:** `256`.
149+
* `scheduling` {string} Scheduling strategy to apply when picking
150+
the next free socket to use. It can be `'fifo'` or `'lifo'`.
151+
The main difference between the two scheduling strategies is that `'lifo'`
152+
selects the most recently used socket, while `'fifo'` selects
153+
the least recently used socket.
154+
In case of a low rate of request per second, the `'lifo'` scheduling
155+
will lower the risk of picking a socket that might have been closed
156+
by the server due to inactivity.
157+
In case of a high rate of request per second,
158+
the `'fifo'` scheduling will maximize the number of open sockets,
159+
while the `'lifo'` scheduling will keep it as low as possible.
160+
**Default:** `'fifo'`.
145161
* `timeout` {number} Socket timeout in milliseconds.
146162
This will set the timeout when the socket is created.
147163

Collapse file

‎lib/_http_agent.js‎

Copy file name to clipboardExpand all lines: lib/_http_agent.js
+10-1Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const { async_id_symbol } = require('internal/async_hooks').symbols;
3838
const {
3939
codes: {
4040
ERR_OUT_OF_RANGE,
41+
ERR_INVALID_OPT_VALUE,
4142
},
4243
} = require('internal/errors');
4344
const { validateNumber } = require('internal/validators');
@@ -102,6 +103,12 @@ function Agent(options) {
102103
this.maxTotalSockets = Infinity;
103104
}
104105

106+
this.scheduling = this.options.scheduling || 'fifo';
107+
108+
if (this.scheduling !== 'fifo' && this.scheduling !== 'lifo') {
109+
throw new ERR_INVALID_OPT_VALUE('scheduling', this.scheduling);
110+
}
111+
105112
this.on('free', (socket, options) => {
106113
const name = this.getName(options);
107114
debug('agent.on(free)', name);
@@ -238,7 +245,9 @@ Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */,
238245
while (freeSockets.length && freeSockets[0].destroyed) {
239246
freeSockets.shift();
240247
}
241-
socket = freeSockets.shift();
248+
socket = this.scheduling === 'fifo' ?
249+
freeSockets.shift() :
250+
freeSockets.pop();
242251
if (!freeSockets.length)
243252
delete this.freeSockets[name];
244253
}
Collapse file
+148Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const http = require('http');
6+
7+
function createServer(count) {
8+
return http.createServer(common.mustCallAtLeast((req, res) => {
9+
// Return the remote port number used for this connection.
10+
res.end(req.socket.remotePort.toString(10));
11+
}), count);
12+
}
13+
14+
function makeRequest(url, agent, callback) {
15+
http
16+
.request(url, { agent }, (res) => {
17+
let data = '';
18+
res.setEncoding('ascii');
19+
res.on('data', (c) => {
20+
data += c;
21+
});
22+
res.on('end', () => {
23+
process.nextTick(callback, data);
24+
});
25+
})
26+
.end();
27+
}
28+
29+
function bulkRequest(url, agent, done) {
30+
const ports = [];
31+
let count = agent.maxSockets;
32+
33+
for (let i = 0; i < agent.maxSockets; i++) {
34+
makeRequest(url, agent, callback);
35+
}
36+
37+
function callback(port) {
38+
count -= 1;
39+
ports.push(port);
40+
if (count === 0) {
41+
done(ports);
42+
}
43+
}
44+
}
45+
46+
function defaultTest() {
47+
const server = createServer(8);
48+
server.listen(0, onListen);
49+
50+
function onListen() {
51+
const url = `http://localhost:${server.address().port}`;
52+
const agent = new http.Agent({
53+
keepAlive: true,
54+
maxSockets: 5
55+
});
56+
57+
bulkRequest(url, agent, (ports) => {
58+
makeRequest(url, agent, (port) => {
59+
assert.strictEqual(ports[0], port);
60+
makeRequest(url, agent, (port) => {
61+
assert.strictEqual(ports[1], port);
62+
makeRequest(url, agent, (port) => {
63+
assert.strictEqual(ports[2], port);
64+
server.close();
65+
agent.destroy();
66+
});
67+
});
68+
});
69+
});
70+
}
71+
}
72+
73+
function fifoTest() {
74+
const server = createServer(8);
75+
server.listen(0, onListen);
76+
77+
function onListen() {
78+
const url = `http://localhost:${server.address().port}`;
79+
const agent = new http.Agent({
80+
keepAlive: true,
81+
maxSockets: 5,
82+
scheduling: 'fifo'
83+
});
84+
85+
bulkRequest(url, agent, (ports) => {
86+
makeRequest(url, agent, (port) => {
87+
assert.strictEqual(ports[0], port);
88+
makeRequest(url, agent, (port) => {
89+
assert.strictEqual(ports[1], port);
90+
makeRequest(url, agent, (port) => {
91+
assert.strictEqual(ports[2], port);
92+
server.close();
93+
agent.destroy();
94+
});
95+
});
96+
});
97+
});
98+
}
99+
}
100+
101+
function lifoTest() {
102+
const server = createServer(8);
103+
server.listen(0, onListen);
104+
105+
function onListen() {
106+
const url = `http://localhost:${server.address().port}`;
107+
const agent = new http.Agent({
108+
keepAlive: true,
109+
maxSockets: 5,
110+
scheduling: 'lifo'
111+
});
112+
113+
bulkRequest(url, agent, (ports) => {
114+
makeRequest(url, agent, (port) => {
115+
assert.strictEqual(ports[ports.length - 1], port);
116+
makeRequest(url, agent, (port) => {
117+
assert.strictEqual(ports[ports.length - 1], port);
118+
makeRequest(url, agent, (port) => {
119+
assert.strictEqual(ports[ports.length - 1], port);
120+
server.close();
121+
agent.destroy();
122+
});
123+
});
124+
});
125+
});
126+
}
127+
}
128+
129+
function badSchedulingOptionTest() {
130+
try {
131+
new http.Agent({
132+
keepAlive: true,
133+
maxSockets: 5,
134+
scheduling: 'filo'
135+
});
136+
} catch (err) {
137+
assert.strictEqual(err.code, 'ERR_INVALID_OPT_VALUE');
138+
assert.strictEqual(
139+
err.message,
140+
'The value "filo" is invalid for option "scheduling"'
141+
);
142+
}
143+
}
144+
145+
defaultTest();
146+
fifoTest();
147+
lifoTest();
148+
badSchedulingOptionTest();

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.