Skip to content

Commit cc29daa

Browse files
committed
net: warn on keep-alive delays truncated to zero
The keep-alive delays are given in milliseconds but the underlying socket options are configured in whole seconds, so a positive value below 1000 ms rounds down to 0. That leaves the system default in place instead of applying the requested timing, and there is nothing to indicate that the value had no effect. Emit a KeepAliveWarning when a positive initialDelay or interval is truncated to zero, and document the behaviour. A value of 0 keeps its documented meaning of leaving the current setting unchanged and does not warn. Two existing tests passed values below 1000 ms that did not match what their comments described; they now use 1000 ms. Refs: #57712 Signed-off-by: Avocado <ujubongbong@gmail.com>
1 parent 4b5e86c commit cc29daa

5 files changed

Lines changed: 137 additions & 4 deletions

File tree

‎doc/api/net.md‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1669,7 +1669,12 @@ corresponding system default unchanged.
16691669

16701670
`initialDelay` and `interval` are specified in milliseconds but the
16711671
underlying socket options are configured in whole seconds; the values are
1672-
divided by `1000` and rounded down before being applied.
1672+
divided by `1000` and rounded down before being applied. A positive value
1673+
below `1000` therefore rounds down to `0`, which leaves the corresponding
1674+
system default unchanged rather than applying the requested timing. Since
1675+
this is rarely intended, a `KeepAliveWarning` process warning is emitted in
1676+
that case. Sub-second timings cannot be expressed: use a value of at least
1677+
`1000` milliseconds.
16731678

16741679
Enabling the keep-alive functionality will set the following socket options:
16751680

‎lib/net.js‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,20 @@ Socket.prototype.setNoDelay = function(enable) {
847847
};
848848

849849

850+
// The underlying socket options are configured in whole seconds, so a positive
851+
// value below 1000 ms is truncated to 0, which leaves the system default in
852+
// place instead of applying the requested timing. Warn so that this is not
853+
// silently ignored.
854+
function warnOnTruncatedKeepAlive(msecs, seconds, name) {
855+
if (seconds === 0 && msecs > 0) {
856+
process.emitWarning(
857+
`The keep-alive ${name} of ${msecs} ms was truncated to 0 seconds and ` +
858+
'has no effect. Use a value of at least 1000 ms.',
859+
'KeepAliveWarning',
860+
);
861+
}
862+
}
863+
850864
Socket.prototype.setKeepAlive = function(enable, initialDelayMsecs,
851865
intervalMsecs, count) {
852866
if (enable !== null && typeof enable === 'object') {
@@ -861,6 +875,11 @@ Socket.prototype.setKeepAlive = function(enable, initialDelayMsecs,
861875
const interval = intervalMsecs === undefined ?
862876
undefined : ~~(intervalMsecs / 1000);
863877

878+
if (enable) {
879+
warnOnTruncatedKeepAlive(initialDelayMsecs, initialDelay, 'initialDelay');
880+
warnOnTruncatedKeepAlive(intervalMsecs, interval, 'interval');
881+
}
882+
864883
if (!this._handle) {
865884
this[kSetKeepAlive] = enable;
866885
this[kSetKeepAliveInitialDelay] = initialDelay;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const net = require('net');
6+
7+
// The keep-alive delays are given in milliseconds but the underlying socket
8+
// options are configured in whole seconds. A positive value below 1000 ms is
9+
// truncated to 0 seconds, which leaves the system default in place instead of
10+
// applying the requested timing. Verifies that this emits a warning rather
11+
// than being silently ignored.
12+
13+
// Warnings are emitted on the process, so the cases run one at a time to keep
14+
// each one's warnings from being observed by the others.
15+
const cases = [
16+
// A delay below 1000 ms is truncated to 0 and warns.
17+
{
18+
configure: (client) => client.setKeepAlive(true, 400),
19+
check: common.mustCall((messages) => {
20+
assert.strictEqual(messages.length, 1);
21+
assert.match(messages[0], /initialDelay of 400 ms/);
22+
assert.match(messages[0], /at least 1000 ms/);
23+
}),
24+
},
25+
// The interval is truncated the same way and warns independently.
26+
{
27+
configure: (client) => client.setKeepAlive(true, 5000, 500),
28+
check: common.mustCall((messages) => {
29+
assert.strictEqual(messages.length, 1);
30+
assert.match(messages[0], /interval of 500 ms/);
31+
}),
32+
},
33+
// Both delays can be truncated by the same call.
34+
{
35+
configure: (client) => client.setKeepAlive(true, 400, 500),
36+
check: common.mustCall((messages) => {
37+
assert.strictEqual(messages.length, 2);
38+
assert.match(messages[0], /initialDelay of 400 ms/);
39+
assert.match(messages[1], /interval of 500 ms/);
40+
}),
41+
},
42+
// The options object form warns as well.
43+
{
44+
configure: (client) => client.setKeepAlive({
45+
enable: true,
46+
initialDelay: 999,
47+
}),
48+
check: common.mustCall((messages) => {
49+
assert.strictEqual(messages.length, 1);
50+
assert.match(messages[0], /initialDelay of 999 ms/);
51+
}),
52+
},
53+
// A delay of at least 1000 ms is applied as requested and does not warn.
54+
{
55+
configure: (client) => client.setKeepAlive(true, 1000),
56+
check: common.mustCall((messages) => assert.deepStrictEqual(messages, [])),
57+
},
58+
// 0 means "leave the current value unchanged" and is not a truncation.
59+
{
60+
configure: (client) => client.setKeepAlive(true, 0),
61+
check: common.mustCall((messages) => assert.deepStrictEqual(messages, [])),
62+
},
63+
// Omitting the delay does not warn.
64+
{
65+
configure: (client) => client.setKeepAlive(true),
66+
check: common.mustCall((messages) => assert.deepStrictEqual(messages, [])),
67+
},
68+
// Nothing is configured when keep-alive is disabled, so there is nothing to
69+
// warn about.
70+
{
71+
configure: (client) => client.setKeepAlive(false, 400),
72+
check: common.mustCall((messages) => assert.deepStrictEqual(messages, [])),
73+
},
74+
];
75+
76+
function runCase({ configure, check }, done) {
77+
const messages = [];
78+
const onWarning = (warning) => {
79+
if (warning.name === 'KeepAliveWarning') messages.push(warning.message);
80+
};
81+
process.on('warning', onWarning);
82+
83+
const server = net.createServer();
84+
server.listen(0, common.mustCall(() => {
85+
const client = net.connect(
86+
{ port: server.address().port },
87+
common.mustCall(() => {
88+
configure(client);
89+
client.end();
90+
}));
91+
92+
client.on('end', common.mustCall(() => {
93+
server.close(common.mustCall(() => {
94+
// Warnings are emitted on the next tick.
95+
setImmediate(() => {
96+
process.removeListener('warning', onWarning);
97+
check(messages);
98+
done();
99+
});
100+
}));
101+
}));
102+
}));
103+
}
104+
105+
(function next(i) {
106+
if (i === cases.length) return;
107+
runCase(cases[i], common.mustCall(() => next(i + 1)));
108+
})(0);

‎test/parallel/test-net-keepalive.js‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ const echoServer = net.createServer(common.mustCall((connection) => {
3838
}, 1), common.platformTimeout(100));
3939
connection.setTimeout(0);
4040
assert.notStrictEqual(connection.setKeepAlive, undefined);
41-
// Send a keepalive packet after 50 ms
42-
connection.setKeepAlive(true, common.platformTimeout(50));
41+
// Send a keepalive packet after 1 second. Values below 1000 ms are
42+
// truncated to 0 seconds and would leave keep-alive unconfigured.
43+
connection.setKeepAlive(true, 1000);
4344
connection.on('end', function() {
4445
connection.end();
4546
});

‎test/parallel/test-net-persistent-keepalive.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ echoServer.on('listening', common.mustCall(function() {
2727
clientConnection = new net.Socket();
2828
// Send a keepalive packet after 1000 ms
2929
// and make sure it persists
30-
const s = clientConnection.setKeepAlive(true, 400);
30+
const s = clientConnection.setKeepAlive(true, 1000);
3131
assert.ok(s instanceof net.Socket);
3232
clientConnection.connect(this.address().port);
3333
clientConnection.setTimeout(0);

0 commit comments

Comments
 (0)