Add disconnect method to forked child processes
authorAndreas Madsen <amwebdk@gmail.com>
Mon, 30 Jan 2012 15:35:05 +0000 (16:35 +0100)
committerBert Belder <bertbelder@gmail.com>
Mon, 30 Jan 2012 15:35:27 +0000 (16:35 +0100)
This disconnect method allows the child to exit gracefully.
This also adds a disconnect event and connect property.

doc/api/child_processes.markdown
lib/child_process.js

index a7c174c..57aa945 100644 (file)
@@ -24,6 +24,13 @@ of the signal, otherwise `null`.
 
 See `waitpid(2)`.
 
+### Event: 'disconnect'
+
+This event is emitted after using the `.disconnect()` method in the parent or
+in the child. After disconnecting it is no longer possible to send messages.
+An alternative way to check if you can send messages is to see if the
+`child.connected` property is `true`.
+
 ### child.stdin
 
 A `Writable Stream` that represents the child process's `stdin`.
@@ -264,7 +271,12 @@ processes:
       }
     });
 
-
+To close the IPC connection between parent and child use the
+`child.disconnect()` method. This allows the child to exit gracefully since
+there is no IPC channel keeping it alive. When calling this method the
+`disconnect` event will be emitted in both parent and child, and the
+`connected` flag will be set to `false`. Please note that you can also call
+`process.disconnect()` in the child process.
 
 ### child.kill([signal])
 
index 9cdf7d6..4af4d4f 100644 (file)
@@ -85,6 +85,7 @@ function setupChannel(target, channel) {
     }
   }
 
+  channel.buffering = false;
   channel.onread = function(pool, offset, length, recvHandle) {
     if (recvHandle && setSimultaneousAccepts) {
       // Update simultaneous accepts on Windows
@@ -117,10 +118,11 @@ function setupChannel(target, channel) {
         start = i + 1;
       }
       jsonBuffer = jsonBuffer.slice(start);
+      this.buffering = jsonBuffer.length !== 0;
 
     } else {
-      channel.close();
-      target._channel = null;
+      this.buffering = false;
+      target.disconnect();
     }
   };
 
@@ -129,7 +131,7 @@ function setupChannel(target, channel) {
       throw new TypeError('message cannot be undefined');
     }
 
-    if (!target._channel) throw new Error("channel closed");
+    if (!this.connected) throw new Error("channel closed");
 
     // For overflow protection don't write if channel queue is too deep.
     if (channel.writeQueueSize > 1024 * 1024) {
@@ -154,6 +156,34 @@ function setupChannel(target, channel) {
     return true;
   };
 
+  target.connected = true;
+  target.disconnect = function() {
+      if (!this.connected) return;
+
+      // do not allow messages to be written
+      this.connected = false;
+      this._channel = null;
+
+      var fired = false;
+      function finish() {
+        if (fired) return;
+        fired = true;
+
+        channel.close();
+        target.emit('disconnect');
+      }
+
+      // If a message is being read, then wait for it to complete.
+      if (channel.buffering) {
+        this.once('message', finish);
+        this.once('internalMessage', finish);
+
+        return;
+      }
+
+      finish();
+  };
+
   channel.readStart();
 }
 
@@ -201,11 +231,8 @@ exports.fork = function(modulePath /*, args, options*/) {
 
   if (!options.thread) setupChannel(child, options.stdinStream);
 
-  child.on('exit', function() {
-    if (child._channel) {
-      child._channel.close();
-    }
-  });
+  // Disconnect when the child process exits.
+  child.once('exit', child.disconnect.bind(child));
 
   return child;
 };