Skip to content

Commit a3a0c0e

Browse files
delvedortargos
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. PR-URL: #33278 Reviewed-By: Robert Nagy <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 4cc5e96 commit a3a0c0e

File tree

3 files changed

+174
-1
lines changed

3 files changed

+174
-1
lines changed

doc/api/http.md

+17
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ http.get({
110110
### `new Agent([options])`
111111
<!-- YAML
112112
added: v0.3.4
113+
changes:
114+
- version: REPLACEME
115+
pr-url: https://github.com/nodejs/node/pull/33278
116+
description: Add `scheduling` option to specify the free socket
117+
scheduling strategy.
113118
-->
114119

115120
* `options` {Object} Set of configurable options to set on the agent.
@@ -132,6 +137,18 @@ added: v0.3.4
132137
* `maxFreeSockets` {number} Maximum number of sockets to leave open
133138
in a free state. Only relevant if `keepAlive` is set to `true`.
134139
**Default:** `256`.
140+
* `scheduling` {string} Scheduling strategy to apply when picking
141+
the next free socket to use. It can be `'fifo'` or `'lifo'`.
142+
The main difference between the two scheduling strategies is that `'lifo'`
143+
selects the most recently used socket, while `'fifo'` selects
144+
the least recently used socket.
145+
In case of a low rate of request per second, the `'lifo'` scheduling
146+
will lower the risk of picking a socket that might have been closed
147+
by the server due to inactivity.
148+
In case of a high rate of request per second,
149+
the `'fifo'` scheduling will maximize the number of open sockets,
150+
while the `'lifo'` scheduling will keep it as low as possible.
151+
**Default:** `'fifo'`.
135152
* `timeout` {number} Socket timeout in milliseconds.
136153
This will set the timeout when the socket is created.
137154

lib/_http_agent.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const { async_id_symbol } = require('internal/async_hooks').symbols;
3535
const {
3636
codes: {
3737
ERR_INVALID_ARG_TYPE,
38+
ERR_INVALID_OPT_VALUE,
3839
},
3940
} = require('internal/errors');
4041
const { once } = require('internal/util');
@@ -86,6 +87,11 @@ function Agent(options) {
8687
this.keepAlive = this.options.keepAlive || false;
8788
this.maxSockets = this.options.maxSockets || Agent.defaultMaxSockets;
8889
this.maxFreeSockets = this.options.maxFreeSockets || 256;
90+
this.scheduling = this.options.scheduling || 'fifo';
91+
92+
if (this.scheduling !== 'fifo' && this.scheduling !== 'lifo') {
93+
throw new ERR_INVALID_OPT_VALUE('scheduling', this.scheduling);
94+
}
8995

9096
this.on('free', (socket, options) => {
9197
const name = this.getName(options);
@@ -219,7 +225,9 @@ Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */,
219225
while (freeSockets.length && freeSockets[0].destroyed) {
220226
freeSockets.shift();
221227
}
222-
socket = freeSockets.shift();
228+
socket = this.scheduling === 'fifo' ?
229+
freeSockets.shift() :
230+
freeSockets.pop();
223231
if (!freeSockets.length)
224232
delete this.freeSockets[name];
225233
}
+148
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)