mirror of
https://github.com/nodejs/node.git
synced 2024-11-24 03:07:54 +01:00
lib: add UV_UDP_REUSEPORT for udp
PR-URL: https://github.com/nodejs/node/pull/55403 Refs: https://github.com/libuv/libuv/pull/4419 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
ee46d2297c
commit
6a02c2701e
@ -343,7 +343,9 @@ used when using `dgram.Socket` objects with the [`cluster`][] module. When
|
||||
`exclusive` is set to `false` (the default), cluster workers will use the same
|
||||
underlying socket handle allowing connection handling duties to be shared.
|
||||
When `exclusive` is `true`, however, the handle is not shared and attempted
|
||||
port sharing results in an error.
|
||||
port sharing results in an error. Creating a `dgram.Socket` with the `reusePort`
|
||||
option set to `true` causes `exclusive` to always be `true` when `socket.bind()`
|
||||
is called.
|
||||
|
||||
A bound datagram socket keeps the Node.js process running to receive
|
||||
datagram messages.
|
||||
@ -916,6 +918,9 @@ chained.
|
||||
<!-- YAML
|
||||
added: v0.11.13
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/55403
|
||||
description: The `reusePort` option is supported.
|
||||
- version: v15.8.0
|
||||
pr-url: https://github.com/nodejs/node/pull/37026
|
||||
description: AbortSignal support was added.
|
||||
@ -935,7 +940,15 @@ changes:
|
||||
* `type` {string} The family of socket. Must be either `'udp4'` or `'udp6'`.
|
||||
Required.
|
||||
* `reuseAddr` {boolean} When `true` [`socket.bind()`][] will reuse the
|
||||
address, even if another process has already bound a socket on it.
|
||||
address, even if another process has already bound a socket on it, but
|
||||
only one socket can receive the data.
|
||||
**Default:** `false`.
|
||||
* `reusePort` {boolean} When `true` [`socket.bind()`][] will reuse the
|
||||
port, even if another process has already bound a socket on it. Incoming
|
||||
datagrams are distributed to listening sockets. The option is available
|
||||
only on some platforms, such as Linux 3.9+, DragonFlyBSD 3.6+, FreeBSD 12.0+,
|
||||
Solaris 11.4, and AIX 7.2.5+. On unsupported platforms this option raises an
|
||||
an error when the socket is bound.
|
||||
**Default:** `false`.
|
||||
* `ipv6Only` {boolean} Setting `ipv6Only` to `true` will
|
||||
disable dual-stack support, i.e., binding to address `::` won't make
|
||||
|
@ -74,7 +74,7 @@ const {
|
||||
const { UV_UDP_REUSEADDR } = internalBinding('constants').os;
|
||||
|
||||
const {
|
||||
constants: { UV_UDP_IPV6ONLY },
|
||||
constants: { UV_UDP_IPV6ONLY, UV_UDP_REUSEPORT },
|
||||
UDP,
|
||||
SendWrap,
|
||||
} = internalBinding('udp_wrap');
|
||||
@ -130,6 +130,7 @@ function Socket(type, listener) {
|
||||
connectState: CONNECT_STATE_DISCONNECTED,
|
||||
queue: undefined,
|
||||
reuseAddr: options?.reuseAddr, // Use UV_UDP_REUSEADDR if true.
|
||||
reusePort: options?.reusePort,
|
||||
ipv6Only: options?.ipv6Only,
|
||||
recvBufferSize,
|
||||
sendBufferSize,
|
||||
@ -345,6 +346,10 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) {
|
||||
flags |= UV_UDP_REUSEADDR;
|
||||
if (state.ipv6Only)
|
||||
flags |= UV_UDP_IPV6ONLY;
|
||||
if (state.reusePort) {
|
||||
exclusive = true;
|
||||
flags |= UV_UDP_REUSEPORT;
|
||||
}
|
||||
|
||||
if (cluster.isWorker && !exclusive) {
|
||||
bindServerHandle(this, {
|
||||
|
@ -231,6 +231,7 @@ void UDPWrap::Initialize(Local<Object> target,
|
||||
Local<Object> constants = Object::New(isolate);
|
||||
NODE_DEFINE_CONSTANT(constants, UV_UDP_IPV6ONLY);
|
||||
NODE_DEFINE_CONSTANT(constants, UV_UDP_REUSEADDR);
|
||||
NODE_DEFINE_CONSTANT(constants, UV_UDP_REUSEPORT);
|
||||
target->Set(context,
|
||||
env->constants_string(),
|
||||
constants).Check();
|
||||
|
24
test/common/udp.js
Normal file
24
test/common/udp.js
Normal file
@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
const dgram = require('dgram');
|
||||
|
||||
const options = { type: 'udp4', reusePort: true };
|
||||
|
||||
function checkSupportReusePort() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = dgram.createSocket(options);
|
||||
socket.bind(0);
|
||||
socket.on('listening', () => {
|
||||
socket.close(resolve);
|
||||
});
|
||||
socket.on('error', (err) => {
|
||||
console.log('The `reusePort` option is not supported:', err.message);
|
||||
socket.close();
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checkSupportReusePort,
|
||||
options,
|
||||
};
|
35
test/parallel/test-child-process-dgram-reuseport.js
Normal file
35
test/parallel/test-child-process-dgram-reuseport.js
Normal file
@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const { checkSupportReusePort, options } = require('../common/udp');
|
||||
const assert = require('assert');
|
||||
const child_process = require('child_process');
|
||||
const dgram = require('dgram');
|
||||
|
||||
if (!process.env.isWorker) {
|
||||
checkSupportReusePort().then(() => {
|
||||
const socket = dgram.createSocket(options);
|
||||
socket.bind(0, common.mustCall(() => {
|
||||
const port = socket.address().port;
|
||||
const workerOptions = { env: { ...process.env, isWorker: 1, port } };
|
||||
let count = 2;
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const worker = child_process.fork(__filename, workerOptions);
|
||||
worker.on('exit', common.mustCall((code) => {
|
||||
assert.strictEqual(code, 0);
|
||||
if (--count === 0) {
|
||||
socket.close();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}));
|
||||
}, () => {
|
||||
common.skip('The `reusePort` is not supported');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const socket = dgram.createSocket(options);
|
||||
|
||||
socket.bind(+process.env.port, common.mustCall(() => {
|
||||
socket.close();
|
||||
})).on('error', common.mustNotCall());
|
39
test/parallel/test-cluster-dgram-reuseport.js
Normal file
39
test/parallel/test-cluster-dgram-reuseport.js
Normal file
@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
if (common.isWindows)
|
||||
common.skip('dgram clustering is currently not supported on windows.');
|
||||
|
||||
const { checkSupportReusePort, options } = require('../common/udp');
|
||||
const assert = require('assert');
|
||||
const cluster = require('cluster');
|
||||
const dgram = require('dgram');
|
||||
|
||||
if (cluster.isPrimary) {
|
||||
checkSupportReusePort().then(() => {
|
||||
cluster.fork().on('exit', common.mustCall((code) => {
|
||||
assert.strictEqual(code, 0);
|
||||
}));
|
||||
}, () => {
|
||||
common.skip('The `reusePort` option is not supported');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let waiting = 2;
|
||||
function close() {
|
||||
if (--waiting === 0)
|
||||
cluster.worker.disconnect();
|
||||
}
|
||||
|
||||
// Test if the worker requests the main process to create a socket
|
||||
cluster._getServer = common.mustNotCall();
|
||||
|
||||
const socket1 = dgram.createSocket(options);
|
||||
const socket2 = dgram.createSocket(options);
|
||||
|
||||
socket1.bind(0, () => {
|
||||
socket2.bind(socket1.address().port, () => {
|
||||
socket1.close(close);
|
||||
socket2.close(close);
|
||||
}).on('error', common.mustNotCall());
|
||||
}).on('error', common.mustNotCall());
|
21
test/parallel/test-dgram-reuseport.js
Normal file
21
test/parallel/test-dgram-reuseport.js
Normal file
@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const { checkSupportReusePort, options } = require('../common/udp');
|
||||
const dgram = require('dgram');
|
||||
|
||||
function test() {
|
||||
const socket1 = dgram.createSocket(options);
|
||||
const socket2 = dgram.createSocket(options);
|
||||
socket1.bind(0, common.mustCall(() => {
|
||||
socket2.bind(socket1.address().port, common.mustCall(() => {
|
||||
socket1.close();
|
||||
socket2.close();
|
||||
}));
|
||||
}));
|
||||
socket1.on('error', common.mustNotCall());
|
||||
socket2.on('error', common.mustNotCall());
|
||||
}
|
||||
|
||||
checkSupportReusePort().then(test, () => {
|
||||
common.skip('The `reusePort` option is not supported');
|
||||
});
|
Loading…
Reference in New Issue
Block a user