Skip to content

Commit dcbc5fb

Browse files
theanarkhaduh95
authored andcommitted
lib: add UV_UDP_REUSEPORT for udp
PR-URL: #55403 Refs: libuv/libuv#4419 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 9e32027 commit dcbc5fb

File tree

7 files changed

+141
-3
lines changed

7 files changed

+141
-3
lines changed

doc/api/dgram.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,9 @@ used when using `dgram.Socket` objects with the [`cluster`][] module. When
343343
`exclusive` is set to `false` (the default), cluster workers will use the same
344344
underlying socket handle allowing connection handling duties to be shared.
345345
When `exclusive` is `true`, however, the handle is not shared and attempted
346-
port sharing results in an error.
346+
port sharing results in an error. Creating a `dgram.Socket` with the `reusePort`
347+
option set to `true` causes `exclusive` to always be `true` when `socket.bind()`
348+
is called.
347349

348350
A bound datagram socket keeps the Node.js process running to receive
349351
datagram messages.
@@ -916,6 +918,9 @@ chained.
916918
<!-- YAML
917919
added: v0.11.13
918920
changes:
921+
- version: REPLACEME
922+
pr-url: https://github.com/nodejs/node/pull/55403
923+
description: The `reusePort` option is supported.
919924
- version: v15.8.0
920925
pr-url: https://github.com/nodejs/node/pull/37026
921926
description: AbortSignal support was added.
@@ -935,7 +940,15 @@ changes:
935940
* `type` {string} The family of socket. Must be either `'udp4'` or `'udp6'`.
936941
Required.
937942
* `reuseAddr` {boolean} When `true` [`socket.bind()`][] will reuse the
938-
address, even if another process has already bound a socket on it.
943+
address, even if another process has already bound a socket on it, but
944+
only one socket can receive the data.
945+
**Default:** `false`.
946+
* `reusePort` {boolean} When `true` [`socket.bind()`][] will reuse the
947+
port, even if another process has already bound a socket on it. Incoming
948+
datagrams are distributed to listening sockets. The option is available
949+
only on some platforms, such as Linux 3.9+, DragonFlyBSD 3.6+, FreeBSD 12.0+,
950+
Solaris 11.4, and AIX 7.2.5+. On unsupported platforms this option raises an
951+
an error when the socket is bound.
939952
**Default:** `false`.
940953
* `ipv6Only` {boolean} Setting `ipv6Only` to `true` will
941954
disable dual-stack support, i.e., binding to address `::` won't make

lib/dgram.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const {
7474
const { UV_UDP_REUSEADDR } = internalBinding('constants').os;
7575

7676
const {
77-
constants: { UV_UDP_IPV6ONLY },
77+
constants: { UV_UDP_IPV6ONLY, UV_UDP_REUSEPORT },
7878
UDP,
7979
SendWrap,
8080
} = internalBinding('udp_wrap');
@@ -130,6 +130,7 @@ function Socket(type, listener) {
130130
connectState: CONNECT_STATE_DISCONNECTED,
131131
queue: undefined,
132132
reuseAddr: options?.reuseAddr, // Use UV_UDP_REUSEADDR if true.
133+
reusePort: options?.reusePort,
133134
ipv6Only: options?.ipv6Only,
134135
recvBufferSize,
135136
sendBufferSize,
@@ -345,6 +346,10 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) {
345346
flags |= UV_UDP_REUSEADDR;
346347
if (state.ipv6Only)
347348
flags |= UV_UDP_IPV6ONLY;
349+
if (state.reusePort) {
350+
exclusive = true;
351+
flags |= UV_UDP_REUSEPORT;
352+
}
348353

349354
if (cluster.isWorker && !exclusive) {
350355
bindServerHandle(this, {

src/udp_wrap.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ void UDPWrap::Initialize(Local<Object> target,
231231
Local<Object> constants = Object::New(isolate);
232232
NODE_DEFINE_CONSTANT(constants, UV_UDP_IPV6ONLY);
233233
NODE_DEFINE_CONSTANT(constants, UV_UDP_REUSEADDR);
234+
NODE_DEFINE_CONSTANT(constants, UV_UDP_REUSEPORT);
234235
target->Set(context,
235236
env->constants_string(),
236237
constants).Check();

test/common/udp.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
const dgram = require('dgram');
3+
4+
const options = { type: 'udp4', reusePort: true };
5+
6+
function checkSupportReusePort() {
7+
return new Promise((resolve, reject) => {
8+
const socket = dgram.createSocket(options);
9+
socket.bind(0);
10+
socket.on('listening', () => {
11+
socket.close(resolve);
12+
});
13+
socket.on('error', (err) => {
14+
console.log('The `reusePort` option is not supported:', err.message);
15+
socket.close();
16+
reject(err);
17+
});
18+
});
19+
}
20+
21+
module.exports = {
22+
checkSupportReusePort,
23+
options,
24+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
const common = require('../common');
3+
const { checkSupportReusePort, options } = require('../common/udp');
4+
const assert = require('assert');
5+
const child_process = require('child_process');
6+
const dgram = require('dgram');
7+
8+
if (!process.env.isWorker) {
9+
checkSupportReusePort().then(() => {
10+
const socket = dgram.createSocket(options);
11+
socket.bind(0, common.mustCall(() => {
12+
const port = socket.address().port;
13+
const workerOptions = { env: { ...process.env, isWorker: 1, port } };
14+
let count = 2;
15+
for (let i = 0; i < 2; i++) {
16+
const worker = child_process.fork(__filename, workerOptions);
17+
worker.on('exit', common.mustCall((code) => {
18+
assert.strictEqual(code, 0);
19+
if (--count === 0) {
20+
socket.close();
21+
}
22+
}));
23+
}
24+
}));
25+
}, () => {
26+
common.skip('The `reusePort` is not supported');
27+
});
28+
return;
29+
}
30+
31+
const socket = dgram.createSocket(options);
32+
33+
socket.bind(+process.env.port, common.mustCall(() => {
34+
socket.close();
35+
})).on('error', common.mustNotCall());
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use strict';
2+
const common = require('../common');
3+
if (common.isWindows)
4+
common.skip('dgram clustering is currently not supported on windows.');
5+
6+
const { checkSupportReusePort, options } = require('../common/udp');
7+
const assert = require('assert');
8+
const cluster = require('cluster');
9+
const dgram = require('dgram');
10+
11+
if (cluster.isPrimary) {
12+
checkSupportReusePort().then(() => {
13+
cluster.fork().on('exit', common.mustCall((code) => {
14+
assert.strictEqual(code, 0);
15+
}));
16+
}, () => {
17+
common.skip('The `reusePort` option is not supported');
18+
});
19+
return;
20+
}
21+
22+
let waiting = 2;
23+
function close() {
24+
if (--waiting === 0)
25+
cluster.worker.disconnect();
26+
}
27+
28+
// Test if the worker requests the main process to create a socket
29+
cluster._getServer = common.mustNotCall();
30+
31+
const socket1 = dgram.createSocket(options);
32+
const socket2 = dgram.createSocket(options);
33+
34+
socket1.bind(0, () => {
35+
socket2.bind(socket1.address().port, () => {
36+
socket1.close(close);
37+
socket2.close(close);
38+
}).on('error', common.mustNotCall());
39+
}).on('error', common.mustNotCall());

test/parallel/test-dgram-reuseport.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
const common = require('../common');
3+
const { checkSupportReusePort, options } = require('../common/udp');
4+
const dgram = require('dgram');
5+
6+
function test() {
7+
const socket1 = dgram.createSocket(options);
8+
const socket2 = dgram.createSocket(options);
9+
socket1.bind(0, common.mustCall(() => {
10+
socket2.bind(socket1.address().port, common.mustCall(() => {
11+
socket1.close();
12+
socket2.close();
13+
}));
14+
}));
15+
socket1.on('error', common.mustNotCall());
16+
socket2.on('error', common.mustNotCall());
17+
}
18+
19+
checkSupportReusePort().then(test, () => {
20+
common.skip('The `reusePort` option is not supported');
21+
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy