current commit
260908.16 · net: support sending net.BoundSocket to threads and child processes
'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);
}));