From 1952844f45f8944f7c6920e836d0deb3de210efd Mon Sep 17 00:00:00 2001 From: cjihrig Date: Tue, 16 Feb 2016 15:12:56 -0500 Subject: [PATCH] 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 Reviewed-By: Ben Noordhuis --- doc/api/child_process.markdown | 6 ++- doc/api/process.markdown | 3 +- lib/internal/child_process.js | 49 +++++++++++++------ .../test-child-process-send-type-error.js | 25 ++++++++++ 4 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 test/parallel/test-child-process-send-type-error.js diff --git a/doc/api/child_process.markdown b/doc/api/child_process.markdown index 5b87a34d88..44b7970850 100644 --- a/doc/api/child_process.markdown +++ b/doc/api/child_process.markdown @@ -750,10 +750,11 @@ console.log(`Spawned child pid: ${grep.pid}`); grep.stdin.end(); ``` -### child.send(message[, sendHandle][, callback]) +### child.send(message[, sendHandle[, options]][, callback]) * `message` {Object} * `sendHandle` {Handle} +* `options` {Object} * `callback` {Function} * 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 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 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. diff --git a/doc/api/process.markdown b/doc/api/process.markdown index c124cfc0a9..ca29713280 100644 --- a/doc/api/process.markdown +++ b/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 relied upon to exist. -## process.send(message[, sendHandle][, callback]) +## process.send(message[, sendHandle[, options]][, callback]) * `message` {Object} * `sendHandle` {Handle object} +* `options` {Object} * `callback` {Function} * Return: {Boolean} diff --git a/lib/internal/child_process.js b/lib/internal/child_process.js index 679999c0f2..7f19d245cc 100644 --- a/lib/internal/child_process.js +++ b/lib/internal/child_process.js @@ -35,7 +35,7 @@ const handleConversion = { 'net.Native': { simultaneousAccepts: true, - send: function(message, handle) { + send: function(message, handle, options) { return handle; }, @@ -47,7 +47,7 @@ const handleConversion = { 'net.Server': { simultaneousAccepts: true, - send: function(message, server) { + send: function(message, server, options) { return server._handle; }, @@ -60,7 +60,7 @@ const handleConversion = { }, 'net.Socket': { - send: function(message, socket) { + send: function(message, socket, options) { if (!socket._handle) return; @@ -90,7 +90,7 @@ const handleConversion = { return handle; }, - postSend: function(handle) { + postSend: function(handle, options) { // Close the Socket handle after sending it if (handle) handle.close(); @@ -117,7 +117,7 @@ const handleConversion = { 'dgram.Native': { simultaneousAccepts: false, - send: function(message, handle) { + send: function(message, handle, options) { return handle; }, @@ -129,7 +129,7 @@ const handleConversion = { 'dgram.Socket': { simultaneousAccepts: false, - send: function(message, socket) { + send: function(message, socket, options) { message.dgramType = socket.type; return socket._handle; @@ -466,7 +466,7 @@ function setupChannel(target, channel) { target._handleQueue = null; 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). @@ -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') { callback = handle; 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) { - return this._send(message, handle, false, callback); + return this._send(message, handle, options, callback); } const ex = new Error('channel closed'); if (typeof callback === 'function') { @@ -515,12 +525,17 @@ function setupChannel(target, channel) { return false; }; - target._send = function(message, handle, swallowErrors, callback) { + target._send = function(message, handle, options, callback) { assert(this.connected || this._channel); if (message === 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 if (handle) { // this message will be handled by an internalMessage event handler @@ -549,6 +564,7 @@ function setupChannel(target, channel) { this._handleQueue.push({ callback: callback, handle: handle, + options: options, message: message.msg, }); return this._handleQueue.length === 1; @@ -557,8 +573,10 @@ function setupChannel(target, channel) { var obj = handleConversion[message.type]; // convert TCP object to native handle object - handle = - handleConversion[message.type].send.call(target, message, handle); + handle = handleConversion[message.type].send.call(target, + message, + handle, + options); // If handle was sent twice, or it is impossible to get native handle // out of it - just send a text without the handle. @@ -575,6 +593,7 @@ function setupChannel(target, channel) { this._handleQueue.push({ callback: callback, handle: null, + options: options, message: message, }); return this._handleQueue.length === 1; @@ -593,7 +612,7 @@ function setupChannel(target, channel) { if (this.async === true) control.unref(); if (obj && obj.postSend) - obj.postSend(handle); + obj.postSend(handle, options); if (typeof callback === 'function') callback(null); }; @@ -605,9 +624,9 @@ function setupChannel(target, channel) { } else { // Cleanup handle on error if (obj && obj.postSend) - obj.postSend(handle); + obj.postSend(handle, options); - if (!swallowErrors) { + if (!options.swallowErrors) { const ex = errnoException(err, 'write'); if (typeof callback === 'function') { process.nextTick(callback, ex); diff --git a/test/parallel/test-child-process-send-type-error.js b/test/parallel/test-child-process-send-type-error.js new file mode 100644 index 0000000000..b92d1ee90b --- /dev/null +++ b/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]);