mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
This adds support for transferring net.BoundSocket instances to other threads via the worker_threads postMessage() transfer list, and for sending them to child processes as the sendHandle argument of subprocess.send(), following on from the BoundSocket introduction. A BoundSocket reserves a port synchronously at construction time. Making it transferable means a port can be reserved on one thread or process and the bound (but not yet listening or connected) TCP handle handed off to another to listen or connect on, without racing on the bind. For threads, BoundSocket implements kTransfer/kTransferList/ kDeserialize, moving the underlying TCP handle with the same mechanism used for net.Socket and net.Server transfer. For child processes, the handleConversion entry reuses the same transfer protocol on the sending side and the same _TransferredBoundSocket deserialization path on the receiving side; the underlying transport is that of cluster's shared-handle scheduling: SCM_RIGHTS on Unix and WSADuplicateSocket on Windows, both of which carry bind state. In both cases the source instance is left in the adopted state: address(), fd() and close() throw ERR_SOCKET_HANDLE_ADOPTED. Transfer requires an un-adopted, open TCP handle, otherwise ERR_WORKER_HANDLE_NOT_TRANSFERABLE is thrown; pipe (path) binds are not transferable and throw ERR_INVALID_HANDLE_TYPE when sent over IPC. On the receiving side the local address is re-derived from the handle rather than trusted from serialized state. Signed-off-by: Guy Bedford <guybedford@gmail.com> PR-URL: https://github.com/nodejs/node/pull/64725 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
80 lines
2.6 KiB
JavaScript
80 lines
2.6 KiB
JavaScript
'use strict';
|
|
|
|
// This test verifies that an un-adopted TCP net.BoundSocket can be sent to a
|
|
// child process as the sendHandle argument of subprocess.send(). The parent
|
|
// binds synchronously to reserve the port, then hands the bound socket off;
|
|
// the child adopts it via server.listen() and accepts connections.
|
|
|
|
const common = require('../common');
|
|
|
|
const assert = require('assert');
|
|
const net = require('net');
|
|
const { fork } = require('child_process');
|
|
|
|
if (process.argv[2] === 'child') {
|
|
process.on('message', common.mustCall((msg, bound) => {
|
|
assert.ok(bound instanceof net.BoundSocket);
|
|
// The bound address survives the send.
|
|
assert.strictEqual(bound.address().port, msg.port);
|
|
|
|
const server = net.createServer(common.mustCall((socket) => {
|
|
socket.end('from-child');
|
|
server.close();
|
|
}));
|
|
server.listen(bound, common.mustCall(() => {
|
|
assert.strictEqual(server.address().port, msg.port);
|
|
// Adoption consumed the bound socket.
|
|
assert.throws(() => bound.address(), {
|
|
code: 'ERR_SOCKET_HANDLE_ADOPTED',
|
|
});
|
|
process.send('listening');
|
|
}));
|
|
}));
|
|
return;
|
|
}
|
|
|
|
const tmpdir = require('../common/tmpdir');
|
|
tmpdir.refresh();
|
|
|
|
const child = fork(__filename, ['child']);
|
|
|
|
// Guards throw synchronously while no handle send is in flight (queued sends
|
|
// defer conversion).
|
|
const pipeBound = new net.BoundSocket({ path: common.PIPE });
|
|
assert.throws(() => child.send('x', pipeBound), {
|
|
code: 'ERR_INVALID_HANDLE_TYPE',
|
|
});
|
|
pipeBound.close();
|
|
|
|
const closedBound = new net.BoundSocket({ host: '127.0.0.1', port: 0 });
|
|
closedBound.close();
|
|
assert.throws(() => child.send('x', closedBound), {
|
|
code: 'ERR_WORKER_HANDLE_NOT_TRANSFERABLE',
|
|
});
|
|
|
|
const bound = new net.BoundSocket({ host: '127.0.0.1', port: 0 });
|
|
const { port } = bound.address();
|
|
|
|
child.send({ port }, bound, common.mustSucceed());
|
|
|
|
// The source is left in the adopted state; further use throws.
|
|
assert.throws(() => bound.address(), { code: 'ERR_SOCKET_HANDLE_ADOPTED' });
|
|
assert.throws(() => bound.fd(), { code: 'ERR_SOCKET_HANDLE_ADOPTED' });
|
|
assert.throws(() => bound.close(), { code: 'ERR_SOCKET_HANDLE_ADOPTED' });
|
|
|
|
child.on('message', common.mustCall((msg) => {
|
|
assert.strictEqual(msg, 'listening');
|
|
const client = net.connect(port, '127.0.0.1');
|
|
client.setEncoding('utf8');
|
|
let response = '';
|
|
client.on('data', (chunk) => { response += chunk; });
|
|
client.on('end', common.mustCall(() => {
|
|
assert.strictEqual(response, 'from-child');
|
|
child.disconnect();
|
|
}));
|
|
}));
|
|
|
|
child.on('exit', common.mustCall((code) => {
|
|
assert.strictEqual(code, 0);
|
|
}));
|