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 <[email protected]> Reviewed-By: James M Snell <[email protected]>
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+
constdgram=require('dgram');
3+
4+
constoptions={type: 'udp4',reusePort: true};
5+
6+
functioncheckSupportReusePort(){
7+
returnnewPromise((resolve,reject)=>{
8+
constsocket=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+
constcommon=require('../common');
3+
const{ checkSupportReusePort, options }=require('../common/udp');
4+
constassert=require('assert');
5+
constchild_process=require('child_process');
6+
constdgram=require('dgram');
7+
8+
if(!process.env.isWorker){
9+
checkSupportReusePort().then(()=>{
10+
constsocket=dgram.createSocket(options);
11+
socket.bind(0,common.mustCall(()=>{
12+
constport=socket.address().port;
13+
constworkerOptions={env: { ...process.env,isWorker: 1, port }};
14+
letcount=2;
15+
for(leti=0;i<2;i++){
16+
constworker=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+
constsocket=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+
constcommon=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+
constassert=require('assert');
8+
constcluster=require('cluster');
9+
constdgram=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+
letwaiting=2;
23+
functionclose(){
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+
constsocket1=dgram.createSocket(options);
32+
constsocket2=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());
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
const{ checkSupportReusePort, options }=require('../common/udp');
4+
constdgram=require('dgram');
5+
6+
functiontest(){
7+
constsocket1=dgram.createSocket(options);
8+
constsocket2=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)