Browse Source

child_process: support options in send()

This commit adds an options object to process.send(). The same
object is propagated to process._send(), the _handleQueue, and the
send() and postSend() functions of the handle converter.

Fixes: https://github.com/nodejs/node/issues/4271
PR-URL: https://github.com/nodejs/node/pull/5283
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
process-exit-stdio-flushing
cjihrig 9 years ago
parent
commit
1952844f45
  1. 6
      doc/api/child_process.markdown
  2. 3
      doc/api/process.markdown
  3. 49
      lib/internal/child_process.js
  4. 25
      test/parallel/test-child-process-send-type-error.js

6
doc/api/child_process.markdown

@ -750,10 +750,11 @@ console.log(`Spawned child pid: ${grep.pid}`);
grep.stdin.end(); grep.stdin.end();
``` ```
### child.send(message[, sendHandle][, callback]) ### child.send(message[, sendHandle[, options]][, callback])
* `message` {Object} * `message` {Object}
* `sendHandle` {Handle} * `sendHandle` {Handle}
* `options` {Object}
* `callback` {Function} * `callback` {Function}
* Return: {Boolean} * Return: {Boolean}
@ -801,6 +802,9 @@ passing a TCP server or socket object to the child process. The child will
receive the object as the second argument passed to the callback function receive the object as the second argument passed to the callback function
registered on the `process.on('message')` event. registered on the `process.on('message')` event.
The `options` argument, if present, is an object used to parameterize the
sending of certain types of handles.
The optional `callback` is a function that is invoked after the message is The optional `callback` is a function that is invoked after the message is
sent but before the child may have received it. The function is called with a sent but before the child may have received it. The function is called with a
single argument: `null` on success, or an [`Error`][] object on failure. single argument: `null` on success, or an [`Error`][] object on failure.

3
doc/api/process.markdown

@ -825,10 +825,11 @@ In custom builds from non-release versions of the source tree, only the
`name` property may be present. The additional properties should not be `name` property may be present. The additional properties should not be
relied upon to exist. relied upon to exist.
## process.send(message[, sendHandle][, callback]) ## process.send(message[, sendHandle[, options]][, callback])
* `message` {Object} * `message` {Object}
* `sendHandle` {Handle object} * `sendHandle` {Handle object}
* `options` {Object}
* `callback` {Function} * `callback` {Function}
* Return: {Boolean} * Return: {Boolean}

49
lib/internal/child_process.js

@ -35,7 +35,7 @@ const handleConversion = {
'net.Native': { 'net.Native': {
simultaneousAccepts: true, simultaneousAccepts: true,
send: function(message, handle) { send: function(message, handle, options) {
return handle; return handle;
}, },
@ -47,7 +47,7 @@ const handleConversion = {
'net.Server': { 'net.Server': {
simultaneousAccepts: true, simultaneousAccepts: true,
send: function(message, server) { send: function(message, server, options) {
return server._handle; return server._handle;
}, },
@ -60,7 +60,7 @@ const handleConversion = {
}, },
'net.Socket': { 'net.Socket': {
send: function(message, socket) { send: function(message, socket, options) {
if (!socket._handle) if (!socket._handle)
return; return;
@ -90,7 +90,7 @@ const handleConversion = {
return handle; return handle;
}, },
postSend: function(handle) { postSend: function(handle, options) {
// Close the Socket handle after sending it // Close the Socket handle after sending it
if (handle) if (handle)
handle.close(); handle.close();
@ -117,7 +117,7 @@ const handleConversion = {
'dgram.Native': { 'dgram.Native': {
simultaneousAccepts: false, simultaneousAccepts: false,
send: function(message, handle) { send: function(message, handle, options) {
return handle; return handle;
}, },
@ -129,7 +129,7 @@ const handleConversion = {
'dgram.Socket': { 'dgram.Socket': {
simultaneousAccepts: false, simultaneousAccepts: false,
send: function(message, socket) { send: function(message, socket, options) {
message.dgramType = socket.type; message.dgramType = socket.type;
return socket._handle; return socket._handle;
@ -466,7 +466,7 @@ function setupChannel(target, channel) {
target._handleQueue = null; target._handleQueue = null;
queue.forEach(function(args) { queue.forEach(function(args) {
target._send(args.message, args.handle, false, args.callback); target._send(args.message, args.handle, args.options, args.callback);
}); });
// Process a pending disconnect (if any). // Process a pending disconnect (if any).
@ -498,13 +498,23 @@ function setupChannel(target, channel) {
}); });
}); });
target.send = function(message, handle, callback) { target.send = function(message, handle, options, callback) {
if (typeof handle === 'function') { if (typeof handle === 'function') {
callback = handle; callback = handle;
handle = undefined; handle = undefined;
options = undefined;
} else if (typeof options === 'function') {
callback = options;
options = undefined;
} else if (options !== undefined &&
(options === null || typeof options !== 'object')) {
throw new TypeError('"options" argument must be an object');
} }
options = Object.assign({swallowErrors: false}, options);
if (this.connected) { if (this.connected) {
return this._send(message, handle, false, callback); return this._send(message, handle, options, callback);
} }
const ex = new Error('channel closed'); const ex = new Error('channel closed');
if (typeof callback === 'function') { if (typeof callback === 'function') {
@ -515,12 +525,17 @@ function setupChannel(target, channel) {
return false; return false;
}; };
target._send = function(message, handle, swallowErrors, callback) { target._send = function(message, handle, options, callback) {
assert(this.connected || this._channel); assert(this.connected || this._channel);
if (message === undefined) if (message === undefined)
throw new TypeError('"message" argument cannot be undefined'); throw new TypeError('"message" argument cannot be undefined');
// Support legacy function signature
if (typeof options === 'boolean') {
options = {swallowErrors: options};
}
// package messages with a handle object // package messages with a handle object
if (handle) { if (handle) {
// this message will be handled by an internalMessage event handler // this message will be handled by an internalMessage event handler
@ -549,6 +564,7 @@ function setupChannel(target, channel) {
this._handleQueue.push({ this._handleQueue.push({
callback: callback, callback: callback,
handle: handle, handle: handle,
options: options,
message: message.msg, message: message.msg,
}); });
return this._handleQueue.length === 1; return this._handleQueue.length === 1;
@ -557,8 +573,10 @@ function setupChannel(target, channel) {
var obj = handleConversion[message.type]; var obj = handleConversion[message.type];
// convert TCP object to native handle object // convert TCP object to native handle object
handle = handle = handleConversion[message.type].send.call(target,
handleConversion[message.type].send.call(target, message, handle); message,
handle,
options);
// If handle was sent twice, or it is impossible to get native handle // If handle was sent twice, or it is impossible to get native handle
// out of it - just send a text without the handle. // out of it - just send a text without the handle.
@ -575,6 +593,7 @@ function setupChannel(target, channel) {
this._handleQueue.push({ this._handleQueue.push({
callback: callback, callback: callback,
handle: null, handle: null,
options: options,
message: message, message: message,
}); });
return this._handleQueue.length === 1; return this._handleQueue.length === 1;
@ -593,7 +612,7 @@ function setupChannel(target, channel) {
if (this.async === true) if (this.async === true)
control.unref(); control.unref();
if (obj && obj.postSend) if (obj && obj.postSend)
obj.postSend(handle); obj.postSend(handle, options);
if (typeof callback === 'function') if (typeof callback === 'function')
callback(null); callback(null);
}; };
@ -605,9 +624,9 @@ function setupChannel(target, channel) {
} else { } else {
// Cleanup handle on error // Cleanup handle on error
if (obj && obj.postSend) if (obj && obj.postSend)
obj.postSend(handle); obj.postSend(handle, options);
if (!swallowErrors) { if (!options.swallowErrors) {
const ex = errnoException(err, 'write'); const ex = errnoException(err, 'write');
if (typeof callback === 'function') { if (typeof callback === 'function') {
process.nextTick(callback, ex); process.nextTick(callback, ex);

25
test/parallel/test-child-process-send-type-error.js

@ -0,0 +1,25 @@
'use strict';
require('../common');
const assert = require('assert');
const cp = require('child_process');
function noop() {}
function fail(proc, args) {
assert.throws(() => {
proc.send.apply(proc, args);
}, /"options" argument must be an object/);
}
let target = process;
if (process.argv[2] !== 'child')
target = cp.fork(__filename, ['child']);
fail(target, ['msg', null, null]);
fail(target, ['msg', null, '']);
fail(target, ['msg', null, 'foo']);
fail(target, ['msg', null, 0]);
fail(target, ['msg', null, NaN]);
fail(target, ['msg', null, 1]);
fail(target, ['msg', null, null, noop]);
Loading…
Cancel
Save