mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00: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
|
`exclusive` is set to `false` (the default), cluster workers will use the same
|
||||||
underlying socket handle allowing connection handling duties to be shared.
|
underlying socket handle allowing connection handling duties to be shared.
|
||||||
When `exclusive` is `true`, however, the handle is not shared and attempted
|
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
|
A bound datagram socket keeps the Node.js process running to receive
|
||||||
datagram messages.
|
datagram messages.
|
||||||
@ -916,6 +918,9 @@ chained.
|
|||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v0.11.13
|
added: v0.11.13
|
||||||
changes:
|
changes:
|
||||||
|
- version: REPLACEME
|
||||||
|
pr-url: https://github.com/nodejs/node/pull/55403
|
||||||
|
description: The `reusePort` option is supported.
|
||||||
- version: v15.8.0
|
- version: v15.8.0
|
||||||
pr-url: https://github.com/nodejs/node/pull/37026
|
pr-url: https://github.com/nodejs/node/pull/37026
|
||||||
description: AbortSignal support was added.
|
description: AbortSignal support was added.
|
||||||
@ -935,7 +940,15 @@ changes:
|
|||||||
* `type` {string} The family of socket. Must be either `'udp4'` or `'udp6'`.
|
* `type` {string} The family of socket. Must be either `'udp4'` or `'udp6'`.
|
||||||
Required.
|
Required.
|
||||||
* `reuseAddr` {boolean} When `true` [`socket.bind()`][] will reuse the
|
* `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`.
|
**Default:** `false`.
|
||||||
* `ipv6Only` {boolean} Setting `ipv6Only` to `true` will
|
* `ipv6Only` {boolean} Setting `ipv6Only` to `true` will
|
||||||
disable dual-stack support, i.e., binding to address `::` won't make
|
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 { UV_UDP_REUSEADDR } = internalBinding('constants').os;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
constants: { UV_UDP_IPV6ONLY },
|
constants: { UV_UDP_IPV6ONLY, UV_UDP_REUSEPORT },
|
||||||
UDP,
|
UDP,
|
||||||
SendWrap,
|
SendWrap,
|
||||||
} = internalBinding('udp_wrap');
|
} = internalBinding('udp_wrap');
|
||||||
@ -130,6 +130,7 @@ function Socket(type, listener) {
|
|||||||
connectState: CONNECT_STATE_DISCONNECTED,
|
connectState: CONNECT_STATE_DISCONNECTED,
|
||||||
queue: undefined,
|
queue: undefined,
|
||||||
reuseAddr: options?.reuseAddr, // Use UV_UDP_REUSEADDR if true.
|
reuseAddr: options?.reuseAddr, // Use UV_UDP_REUSEADDR if true.
|
||||||
|
reusePort: options?.reusePort,
|
||||||
ipv6Only: options?.ipv6Only,
|
ipv6Only: options?.ipv6Only,
|
||||||
recvBufferSize,
|
recvBufferSize,
|
||||||
sendBufferSize,
|
sendBufferSize,
|
||||||
@ -345,6 +346,10 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) {
|
|||||||
flags |= UV_UDP_REUSEADDR;
|
flags |= UV_UDP_REUSEADDR;
|
||||||
if (state.ipv6Only)
|
if (state.ipv6Only)
|
||||||
flags |= UV_UDP_IPV6ONLY;
|
flags |= UV_UDP_IPV6ONLY;
|
||||||
|
if (state.reusePort) {
|
||||||
|
exclusive = true;
|
||||||
|
flags |= UV_UDP_REUSEPORT;
|
||||||
|
}
|
||||||
|
|
||||||
if (cluster.isWorker && !exclusive) {
|
if (cluster.isWorker && !exclusive) {
|
||||||
bindServerHandle(this, {
|
bindServerHandle(this, {
|
||||||
|
@ -231,6 +231,7 @@ void UDPWrap::Initialize(Local<Object> target,
|
|||||||
Local<Object> constants = Object::New(isolate);
|
Local<Object> constants = Object::New(isolate);
|
||||||
NODE_DEFINE_CONSTANT(constants, UV_UDP_IPV6ONLY);
|
NODE_DEFINE_CONSTANT(constants, UV_UDP_IPV6ONLY);
|
||||||
NODE_DEFINE_CONSTANT(constants, UV_UDP_REUSEADDR);
|
NODE_DEFINE_CONSTANT(constants, UV_UDP_REUSEADDR);
|
||||||
|
NODE_DEFINE_CONSTANT(constants, UV_UDP_REUSEPORT);
|
||||||
target->Set(context,
|
target->Set(context,
|
||||||
env->constants_string(),
|
env->constants_string(),
|
||||||
constants).Check();
|
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