child_process: fix handleless NODE_HANDLE handling
It is possible that `recvmsg()` may return an error on ancillary data reception when receiving a `NODE_HANDLE` message (for example `MSG_CTRUNC`). This would end up, if the handle type was `net.Socket`, on a `message` event with a non null but invalid `sendHandle`. To improve the situation, send a `NODE_HANDLE_NACK` that'll cause the sending process to retransmit the message again. In case the same message is retransmitted 3 times without success, close the handle and print a warning. PR-URL: https://github.com/nodejs/node/pull/13235 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
parent
732ae419b4
commit
71ca122def
@ -24,6 +24,8 @@ const errnoException = util._errnoException;
|
|||||||
const SocketListSend = SocketList.SocketListSend;
|
const SocketListSend = SocketList.SocketListSend;
|
||||||
const SocketListReceive = SocketList.SocketListReceive;
|
const SocketListReceive = SocketList.SocketListReceive;
|
||||||
|
|
||||||
|
const MAX_HANDLE_RETRANSMISSIONS = 3;
|
||||||
|
|
||||||
// this object contain function to convert TCP objects to native handle objects
|
// this object contain function to convert TCP objects to native handle objects
|
||||||
// and back again.
|
// and back again.
|
||||||
const handleConversion = {
|
const handleConversion = {
|
||||||
@ -89,17 +91,18 @@ const handleConversion = {
|
|||||||
return handle;
|
return handle;
|
||||||
},
|
},
|
||||||
|
|
||||||
postSend: function(handle, options, target) {
|
postSend: function(message, handle, options, callback, target) {
|
||||||
// Store the handle after successfully sending it, so it can be closed
|
// Store the handle after successfully sending it, so it can be closed
|
||||||
// when the NODE_HANDLE_ACK is received. If the handle could not be sent,
|
// when the NODE_HANDLE_ACK is received. If the handle could not be sent,
|
||||||
// just close it.
|
// just close it.
|
||||||
if (handle && !options.keepOpen) {
|
if (handle && !options.keepOpen) {
|
||||||
if (target) {
|
if (target) {
|
||||||
// There can only be one _pendingHandle as passing handles are
|
// There can only be one _pendingMessage as passing handles are
|
||||||
// processed one at a time: handles are stored in _handleQueue while
|
// processed one at a time: handles are stored in _handleQueue while
|
||||||
// waiting for the NODE_HANDLE_ACK of the current passing handle.
|
// waiting for the NODE_HANDLE_ACK of the current passing handle.
|
||||||
assert(!target._pendingHandle);
|
assert(!target._pendingMessage);
|
||||||
target._pendingHandle = handle;
|
target._pendingMessage =
|
||||||
|
{ callback, message, handle, options, retransmissions: 0 };
|
||||||
} else {
|
} else {
|
||||||
handle.close();
|
handle.close();
|
||||||
}
|
}
|
||||||
@ -250,6 +253,11 @@ function getHandleWrapType(stream) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closePendingHandle(target) {
|
||||||
|
target._pendingMessage.handle.close();
|
||||||
|
target._pendingMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ChildProcess.prototype.spawn = function(options) {
|
ChildProcess.prototype.spawn = function(options) {
|
||||||
var ipc;
|
var ipc;
|
||||||
@ -435,7 +443,7 @@ function setupChannel(target, channel) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
target._handleQueue = null;
|
target._handleQueue = null;
|
||||||
target._pendingHandle = null;
|
target._pendingMessage = null;
|
||||||
|
|
||||||
const control = new Control(channel);
|
const control = new Control(channel);
|
||||||
|
|
||||||
@ -491,16 +499,31 @@ function setupChannel(target, channel) {
|
|||||||
// handlers will go through this
|
// handlers will go through this
|
||||||
target.on('internalMessage', function(message, handle) {
|
target.on('internalMessage', function(message, handle) {
|
||||||
// Once acknowledged - continue sending handles.
|
// Once acknowledged - continue sending handles.
|
||||||
|
if (message.cmd === 'NODE_HANDLE_ACK' ||
|
||||||
|
message.cmd === 'NODE_HANDLE_NACK') {
|
||||||
|
|
||||||
|
if (target._pendingMessage) {
|
||||||
if (message.cmd === 'NODE_HANDLE_ACK') {
|
if (message.cmd === 'NODE_HANDLE_ACK') {
|
||||||
if (target._pendingHandle) {
|
closePendingHandle(target);
|
||||||
target._pendingHandle.close();
|
} else if (target._pendingMessage.retransmissions++ ===
|
||||||
target._pendingHandle = null;
|
MAX_HANDLE_RETRANSMISSIONS) {
|
||||||
|
closePendingHandle(target);
|
||||||
|
process.emitWarning('Handle did not reach the receiving process ' +
|
||||||
|
'correctly', 'SentHandleNotReceivedWarning');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(Array.isArray(target._handleQueue));
|
assert(Array.isArray(target._handleQueue));
|
||||||
var queue = target._handleQueue;
|
var queue = target._handleQueue;
|
||||||
target._handleQueue = null;
|
target._handleQueue = null;
|
||||||
|
|
||||||
|
if (target._pendingMessage) {
|
||||||
|
target._send(target._pendingMessage.message,
|
||||||
|
target._pendingMessage.handle,
|
||||||
|
target._pendingMessage.options,
|
||||||
|
target._pendingMessage.callback);
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < queue.length; i++) {
|
for (var i = 0; i < queue.length; i++) {
|
||||||
var args = queue[i];
|
var args = queue[i];
|
||||||
target._send(args.message, args.handle, args.options, args.callback);
|
target._send(args.message, args.handle, args.options, args.callback);
|
||||||
@ -515,6 +538,12 @@ function setupChannel(target, channel) {
|
|||||||
|
|
||||||
if (message.cmd !== 'NODE_HANDLE') return;
|
if (message.cmd !== 'NODE_HANDLE') return;
|
||||||
|
|
||||||
|
// It is possible that the handle is not received because of some error on
|
||||||
|
// ancillary data reception such as MSG_CTRUNC. In this case, report the
|
||||||
|
// sender about it by sending a NODE_HANDLE_NACK message.
|
||||||
|
if (!handle)
|
||||||
|
return target._send({ cmd: 'NODE_HANDLE_NACK' }, null, true);
|
||||||
|
|
||||||
// Acknowledge handle receival. Don't emit error events (for example if
|
// Acknowledge handle receival. Don't emit error events (for example if
|
||||||
// the other side has disconnected) because this call to send() is not
|
// the other side has disconnected) because this call to send() is not
|
||||||
// initiated by the user and it shouldn't be fatal to be unable to ACK
|
// initiated by the user and it shouldn't be fatal to be unable to ACK
|
||||||
@ -625,7 +654,8 @@ function setupChannel(target, channel) {
|
|||||||
net._setSimultaneousAccepts(handle);
|
net._setSimultaneousAccepts(handle);
|
||||||
}
|
}
|
||||||
} else if (this._handleQueue &&
|
} else if (this._handleQueue &&
|
||||||
!(message && message.cmd === 'NODE_HANDLE_ACK')) {
|
!(message && (message.cmd === 'NODE_HANDLE_ACK' ||
|
||||||
|
message.cmd === 'NODE_HANDLE_NACK'))) {
|
||||||
// Queue request anyway to avoid out-of-order messages.
|
// Queue request anyway to avoid out-of-order messages.
|
||||||
this._handleQueue.push({
|
this._handleQueue.push({
|
||||||
callback: callback,
|
callback: callback,
|
||||||
@ -647,7 +677,7 @@ function setupChannel(target, channel) {
|
|||||||
if (!this._handleQueue)
|
if (!this._handleQueue)
|
||||||
this._handleQueue = [];
|
this._handleQueue = [];
|
||||||
if (obj && obj.postSend)
|
if (obj && obj.postSend)
|
||||||
obj.postSend(handle, options, target);
|
obj.postSend(message, handle, options, callback, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.async) {
|
if (req.async) {
|
||||||
@ -663,7 +693,7 @@ 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, options);
|
obj.postSend(message, handle, options, callback);
|
||||||
|
|
||||||
if (!options.swallowErrors) {
|
if (!options.swallowErrors) {
|
||||||
const ex = errnoException(err, 'write');
|
const ex = errnoException(err, 'write');
|
||||||
@ -712,10 +742,8 @@ function setupChannel(target, channel) {
|
|||||||
// This marks the fact that the channel is actually disconnected.
|
// This marks the fact that the channel is actually disconnected.
|
||||||
this.channel = null;
|
this.channel = null;
|
||||||
|
|
||||||
if (this._pendingHandle) {
|
if (this._pendingMessage)
|
||||||
this._pendingHandle.close();
|
closePendingHandle(this);
|
||||||
this._pendingHandle = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var fired = false;
|
var fired = false;
|
||||||
function finish() {
|
function finish() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user