Skip to content

Commit 0da4e0e

Browse files
orangemochatrevnorris
authored andcommitted
child_process: don't crash process on internal ops
1. Swallow errors when sending internal NODE_HANDLE_ACK messages, so they don't crash the process. 2. Queue process.disconnect() if there are any pending queued messages. Fixes test-child-process-fork-net2.js on win.
1 parent 06b1945 commit 0da4e0e

File tree

2 files changed

+57
-16
lines changed

2 files changed

+57
-16
lines changed

lib/child_process.js

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -369,16 +369,25 @@ function setupChannel(target, channel) {
369369
assert(util.isArray(target._handleQueue));
370370
var queue = target._handleQueue;
371371
target._handleQueue = null;
372+
372373
queue.forEach(function(args) {
373-
target.send(args.message, args.handle);
374+
target._send(args.message, args.handle, false);
374375
});
376+
377+
// Process a pending disconnect (if any).
378+
if (!target.connected && target._channel && !target._handleQueue)
379+
target._disconnect();
380+
375381
return;
376382
}
377383

378384
if (message.cmd !== 'NODE_HANDLE') return;
379385

380-
// Acknowledge handle receival.
381-
target.send({ cmd: 'NODE_HANDLE_ACK' });
386+
// Acknowledge handle receival. Don't emit error events (for example if
387+
// the other side has disconnected) because this call to send() is not
388+
// initiated by the user and it shouldn't be fatal to be unable to ACK
389+
// a message.
390+
target._send({ cmd: 'NODE_HANDLE_ACK' }, null, true);
382391

383392
var obj = handleConversion[message.type];
384393

@@ -395,14 +404,17 @@ function setupChannel(target, channel) {
395404
});
396405

397406
target.send = function(message, handle) {
398-
if (util.isUndefined(message)) {
399-
throw new TypeError('message cannot be undefined');
400-
}
401-
402-
if (!this.connected) {
407+
if (!this.connected)
403408
this.emit('error', new Error('channel closed'));
404-
return;
405-
}
409+
else
410+
this._send(message, handle, false);
411+
};
412+
413+
target._send = function(message, handle, swallowErrors) {
414+
assert(this.connected || this._channel);
415+
416+
if (util.isUndefined(message))
417+
throw new TypeError('message cannot be undefined');
406418

407419
// package messages with a handle object
408420
if (handle) {
@@ -454,7 +466,8 @@ function setupChannel(target, channel) {
454466
var err = channel.writeUtf8String(req, string, handle);
455467

456468
if (err) {
457-
this.emit('error', errnoException(err, 'write'));
469+
if (!swallowErrors)
470+
this.emit('error', errnoException(err, 'write'));
458471
} else if (handle && !this._handleQueue) {
459472
this._handleQueue = [];
460473
}
@@ -467,15 +480,37 @@ function setupChannel(target, channel) {
467480
return channel.writeQueueSize < (65536 * 2);
468481
};
469482

483+
// connected will be set to false immediately when a disconnect() is
484+
// requested, even though the channel might still be alive internally to
485+
// process queued messages. The three states are distinguished as follows:
486+
// - disconnect() never requested: _channel is not null and connected
487+
// is true
488+
// - disconnect() requested, messages in the queue: _channel is not null
489+
// and connected is false
490+
// - disconnect() requested, channel actually disconnected: _channel is
491+
// null and connected is false
470492
target.connected = true;
493+
471494
target.disconnect = function() {
472495
if (!this.connected) {
473496
this.emit('error', new Error('IPC channel is already disconnected'));
474497
return;
475498
}
476499

477-
// do not allow messages to be written
500+
// Do not allow any new messages to be written.
478501
this.connected = false;
502+
503+
// If there are no queued messages, disconnect immediately. Otherwise,
504+
// postpone the disconnect so that it happens internally after the
505+
// queue is flushed.
506+
if (!this._handleQueue)
507+
this._disconnect();
508+
}
509+
510+
target._disconnect = function() {
511+
assert(this._channel);
512+
513+
// This marks the fact that the channel is actually disconnected.
479514
this._channel = null;
480515

481516
var fired = false;

test/simple/test-child-process-fork-net2.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ if (process.argv[2] === 'child') {
5252
needEnd.push(socket);
5353
}
5454

55-
socket.on('close', function() {
56-
console.error('[%d] socket.close', id, m);
55+
socket.on('close', function(had_error) {
56+
console.error('[%d] socket.close', id, had_error, m);
5757
});
5858

5959
socket.on('finish', function() {
@@ -65,15 +65,15 @@ if (process.argv[2] === 'child') {
6565
if (m !== 'close') return;
6666
console.error('[%d] got close message', id);
6767
needEnd.forEach(function(endMe, i) {
68-
console.error('[%d] ending %d', id, i);
68+
console.error('[%d] ending %d/%d', id, i, needEnd.length);
6969
endMe.end('end');
7070
});
7171
});
7272

7373
process.on('disconnect', function() {
7474
console.error('[%d] process disconnect, ending', id);
7575
needEnd.forEach(function(endMe, i) {
76-
console.error('[%d] ending %d', id, i);
76+
console.error('[%d] ending %d/%d', id, i, needEnd.length);
7777
endMe.end('end');
7878
});
7979
});
@@ -120,6 +120,11 @@ if (process.argv[2] === 'child') {
120120
var j = count, client;
121121
while (j--) {
122122
client = net.connect(common.PORT, '127.0.0.1');
123+
client.on('error', function() {
124+
// This can happen if we kill the child too early.
125+
// The client should still get a close event afterwards.
126+
console.error('[m] CLIENT: error event');
127+
});
123128
client.on('close', function() {
124129
console.error('[m] CLIENT: close event');
125130
disconnected += 1;
@@ -136,6 +141,7 @@ if (process.argv[2] === 'child') {
136141
console.error('[m] server close');
137142
closeEmitted = true;
138143

144+
console.error('[m] killing child processes');
139145
child1.kill();
140146
child2.kill();
141147
child3.kill();

0 commit comments

Comments
 (0)