superchild
Version:
A smarter replacement for node.js's child_process module.
560 lines (425 loc) • 28.4 kB
HTML
<html>
<head>
<title>superchild.js</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" media="all" href="public/stylesheets/normalize.css" />
<link rel="stylesheet" media="all" href="docco.css" />
</head>
<body>
<div class="container">
<div class="page">
<div class="header">
<h1>superchild.js</h1>
<div class="toc">
<h3>Table of Contents</h3>
<ol>
<li>
<a class="source" href="json-sieve.html">
json-sieve.js
</a>
</li>
<li>
<a class="source" href="superchild.html">
superchild.js
</a>
</li>
<li>
<a class="source" href="unlogger.html">
unlogger.js
</a>
</li>
</ol>
</div>
</div>
<p><a href="https://circleci.com/gh/mayanklahiri/node-superchild"><img src="https://circleci.com/gh/mayanklahiri/node-superchild.svg?style=svg" alt="Circle CI"></a>
<a href="https://github.com/mayanklahiri/node-superchild/issues"><img src="https://img.shields.io/github/issues/mayanklahiri/node-superchild.svg" alt="GitHub issues"></a>
<a href="https://github.com/mayanklahiri/node-superchild/stargazers"><img src="https://img.shields.io/github/stars/mayanklahiri/node-superchild.svg" alt="GitHub stars"></a>
<a href="https://raw.githubusercontent.com/mayanklahiri/node-superchild/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="GitHub license"></a>
<a href="https://badge.fury.io/js/superchild"><img src="https://badge.fury.io/js/superchild.svg" alt="npm version"></a>
<a href="https://david-dm.org/mayanklahiri/node-superchild.svg"><img src="https://david-dm.org/mayanklahiri/node-superchild.svg" alt="dependencies"></a>
<a href="https://www.bithound.io/github/mayanklahiri/node-superchild"><img src="https://www.bithound.io/github/mayanklahiri/node-superchild/badges/score.svg" alt="bitHound Overall Score"></a>
<a href="https://www.bithound.io/github/mayanklahiri/node-superchild"><img src="https://www.bithound.io/github/mayanklahiri/node-superchild/badges/code.svg" alt="bitHound Code"></a>
<a href="https://twitter.com/intent/tweet?text=Wow:&url=%5Bobject%20Object%5D"><img src="https://img.shields.io/twitter/url/https/github.com/mayanklahiri/node-superchild.svg?style=social" alt="Twitter"></a></p>
<p><strong>Superchild</strong> is a POSIX-only (e.g., Linux, Mac OS X) wrapper around
node.js’s built-in <code>child_process</code> module, which requires a lot of
care and attention to use correctly.</p>
<p>Links:</p>
<ul>
<li><a href="http://mayanklahiri.github.io/node-superchild/superchild.html">Annotated source code</a></li>
<li><a href="http://mayanklahiri.github.io/node-superchild/coverage/lib/index.html">Current test coverage</a></li>
<li><a href="https://github.com/mayanklahiri/node-superchild">Github page</a></li>
<li><a href="https://www.npmjs.com/package/superchild">NPM page</a></li>
</ul>
<p>The purpose of Superchild is to allow large node.js programs
to be split into independent processes (and sub-processes, resulting
in <strong>process trees</strong>), while handling the tedious parts of process
management and communication.</p>
<p>Superchild aims to be <strong>compatible</strong> with any program
that reads from and writes to their <code>stdin</code>, <code>stdout</code>, and <code>stderr</code>
streams, regardless of what language the program is written in.
This allows interesting hacks like using <code>ssh</code> to execute a
module on a remote host, written in another language, over an
encrypted link, while using the near-universal format of
line-delimited JSON messages over <code>stdio</code>.</p>
<p><strong>Features</strong> that make Superchild different from node’s built-in
<code>child_process</code> module include the following (many of these
are currently possible only due to restricting focus to POSIX
platforms, i.e., not Windows):</p>
<ol>
<li><p>A single function to replace <code>fork()</code>, <code>exec()</code>,
and <code>spawn()</code> from the built-in <code>child_process</code> module.</p>
</li>
<li><p>Waits for <code>stdout</code> and <code>stderr</code> streams to end before
emitting an <code>exit</code> event, unlike <code>child_process.ChildProcess</code>.</p>
</li>
<li><p>Handles isolating child process and its children in a
separate, <strong>detached process group</strong> that can be terminated
as a subtree using the POSIX <code>kill</code> command. This means
that calling <code>close()</code> on a Superchild instance will kill
not just the child process, but all <em>its</em> child processes
and so on (i.e., the entire process group lead by the child).
Note that if any processes in the sub-tree detach themselves
into a new process group, they will not be part of our
child’s process group, and will not be killed.</p>
</li>
<li><p>Handles <strong>graceful termination</strong> of child’s entire process group
using <code>SIGTERM</code> -> <code>SIGKILL</code> signals with a configurable timeout.</p>
</li>
<li><p>Handles <strong>unexpected termination</strong> of the <em>current</em> process by
killing the child’s entire process group immediately with <code>SIGKILL</code>.</p>
</li>
<li><p>Automatically serializes and deserializes <strong>line-delimited JSON</strong>
values (LD-JSON) sent to and received from child, intermixed
with <code>stdout</code>. <code>stderr</code> is passed through unbuffered. Effectively,
this means that the child’s <code>stdout</code> stream is demultiplexed
into the child streams <code>stdout_line</code> (parsed raw text lines),
<code>json_object</code> (parsed JSON objects), and <code>json_array</code> (parsed JSON
arrays). Regular processes have 3 I/O streams (stdin, stdout,
stderr); Superchildren have 6 streams (stdin, stdout, stderr,
stdout_line, json_object, json_array).</p>
</li>
</ol>
<h2 id="install">Install</h2>
<pre><code> npm install superchild
</code></pre><h2 id="usage">Usage</h2>
<h5 id="run-a-shell-command">Run a shell command</h5>
<p>Get a directory listing line-by-line using <code>ls</code>.</p>
<pre><code> <span class="hljs-keyword">var</span> superchild = <span class="hljs-built_in">require</span>(<span class="hljs-string">'superchild'</span>);
<span class="hljs-keyword">var</span> child = superchild(<span class="hljs-string">'ls -lh'</span>);
child.on(<span class="hljs-string">'stdout_line'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">line</span>) </span>{
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'[stdout]: '</span>, line);
});
</code></pre><h5 id="spawn-and-communicate-with-a-module">Spawn and communicate with a module</h5>
<p>Spawn a node.js module in another process and communicate with it.
Note that the child uses the <code>superchild.unlogger</code> helper function
to parse its standard input for LD-JSON arrays and objects.</p>
<p><em>master.js</em></p>
<pre><code> <span class="hljs-keyword">var</span> assert = <span class="hljs-built_in">require</span>(<span class="hljs-string">'assert'</span>);
<span class="hljs-keyword">var</span> superchild = <span class="hljs-built_in">require</span>(<span class="hljs-string">'superchild'</span>);
<span class="hljs-keyword">var</span> child = superchild(<span class="hljs-string">'node echo.js'</span>);
child.send({
some: <span class="hljs-string">'data'</span>,
});
child.on(<span class="hljs-string">'json_object'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">jsonObj</span>) </span>{
assert.equal(jsonObj.some, <span class="hljs-string">'data'</span>);
});
</code></pre><p><em>echo.js</em></p>
<pre><code> <span class="hljs-keyword">var</span> unlogger = <span class="hljs-built_in">require</span>(<span class="hljs-string">'superchild'</span>).unlogger;
unlogger().on(<span class="hljs-string">'json_object'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">jsonObj</span>) </span>{
<span class="hljs-comment">// Echo JSON object from parent back to parent.</span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">JSON</span>.stringify(jsonObj));
});
</code></pre><h2 id="events-emitted">Events emitted</h2>
<p>Superchild is an EventEmitter. The following events can be listened for
using <code>child.on()</code> and <code>child.once()</code> functions.</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Arguments</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>exit</code></td>
<td><code>code</code>, <code>signal</code></td>
<td>Child process exited, identical to <code>child_process.exit</code></td>
</tr>
<tr>
<td><code>stderr_data</code></td>
<td><code>dataStr</code></td>
<td>Received unbuffered data on child’s <code>stderr</code> stream.</td>
</tr>
<tr>
<td><code>stdout_line</code></td>
<td><code>lineStr</code></td>
<td>Received a full line of text from the child process.</td>
</tr>
<tr>
<td><code>json_object</code></td>
<td><code>jsonObj</code></td>
<td>Parsed a line-delimited JSON object from child’s <code>stdout</code> stream.</td>
</tr>
<tr>
<td><code>json_array</code></td>
<td><code>jsonArr</code></td>
<td>Parsed a line-delimited JSON array from child’s <code>stdout</code> stream.</td>
</tr>
</tbody>
</table>
<h2 id="methods">Methods</h2>
<table>
<thead>
<tr>
<th>Method</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>send(jsonVal)</code></td>
<td>Serialize and send a JSON-serializable object or array to the child as LD-JSON.</td>
</tr>
<tr>
<td><code>close(cb)</code></td>
<td>Gracefully terminate the child, invoking the callback when the child has died.</td>
</tr>
</tbody>
</table>
<h2 id="requirements">Requirements</h2>
<ul>
<li><code>node.js</code> version 0.11.13 or higher, due to the use of <code>spawnSync</code>.</li>
<li>POSIX-compliant platform, such as Linux or Mac OS.</li>
</ul>
<h2 id="source-code">Source Code</h2>
<p>The full annotated source code of <code>superchild.js</code> follows, generated
automatically by Docco.</p>
<div class='highlight'><pre><span class="hljs-comment">/** @license The MIT License, Copyright (c) 2016 Mayank Lahiri <mayank@iceroad.io> */</span>
<span class="hljs-keyword">var</span> events = <span class="hljs-built_in">require</span>(<span class="hljs-string">'events'</span>)
, fmt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'util'</span>).format
, fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)
, spawn = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./respawn'</span>).spawn
, spawnSync = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./respawn'</span>).spawnSync
, JSONSieve = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./json-sieve'</span>)
;
<span class="hljs-keyword">var</span> NODE_MAJOR = <span class="hljs-built_in">parseInt</span>(process.version.replace(<span class="hljs-regexp">/\..+/</span>, <span class="hljs-string">''</span>).substr(<span class="hljs-number">1</span>), <span class="hljs-number">10</span>);
<span class="hljs-keyword">var</span> LEGACY_NODE = NODE_MAJOR < <span class="hljs-number">5</span>;
<span class="hljs-keyword">var</span> CLEANUP_PROCESSES = {};
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">superchild</span>(<span class="hljs-params">commandLine, options</span>) </span>{
<span class="hljs-keyword">if</span> (!commandLine) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Superchild: empty commandLine provided.'</span>);
options = (<span class="hljs-keyword">typeof</span> options == <span class="hljs-string">'object'</span> ? options : {});
<span class="hljs-keyword">var</span> opt = {}, setDefault = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">key, defaultVal</span>) </span>{
opt[key] = (key <span class="hljs-keyword">in</span> options) ? options[key] : defaultVal;
};</pre></div>
<h4 id="default-options">Default options</h4>
<ul>
<li><strong><code>stringEncoding</code></strong>: character encoding to use while communicating
with the child. This is required to make node.js return strings instead
of raw buffers. Defaults to <strong>utf-8</strong>.</li>
</ul>
<div class='highlight'><pre> setDefault(<span class="hljs-string">'stringEncoding'</span>, <span class="hljs-string">'utf-8'</span>);</pre></div>
<ul>
<li><strong><code>killOnExit</code></strong>: kill child process group when our own process
exits, if the child is still running. Defaults to <strong>true</strong>.</li>
</ul>
<div class='highlight'><pre> setDefault(<span class="hljs-string">'killOnExit'</span>, <span class="hljs-literal">true</span>);</pre></div>
<ul>
<li><strong><code>cleanupTimeoutMs</code></strong>: amount of walltime to wait for child process
to exit after <code>close()</code> is called, before sending it SIGTERM. Defaults
to <strong>500ms</strong>.</li>
</ul>
<div class='highlight'><pre> setDefault(<span class="hljs-string">'cleanupTimeoutMs'</span>, <span class="hljs-number">500</span>);</pre></div>
<ul>
<li><strong><code>shell</code></strong>: run the command line through a system shell, defaults
to <strong>true</strong>.</li>
</ul>
<div class='highlight'><pre> setDefault(<span class="hljs-string">'shell'</span>, <span class="hljs-literal">true</span>);</pre></div>
<ul>
<li><strong><code>cwd</code></strong>: working directory to execute child process in, defaults
to <strong>process.cwd()</strong>.</li>
</ul>
<div class='highlight'><pre> setDefault(<span class="hljs-string">'cwd'</span>, process.cwd());</pre></div>
<ul>
<li><strong><code>detached</code></strong>: makes the process the leader of its own process group.</li>
</ul>
<div class='highlight'><pre> setDefault(<span class="hljs-string">'detached'</span>, <span class="hljs-literal">true</span>);</pre></div>
<ul>
<li><strong><code>env</code></strong>: environment variables to pass to child.</li>
</ul>
<div class='highlight'><pre> setDefault(<span class="hljs-string">'env'</span>, <span class="hljs-literal">undefined</span>);</pre></div>
<p>Construct an <code>EventEmitter</code> instance to return to the caller. This
is a proxy emitter for the child process so that we can
re-order events received from the child process if necessary.</p>
<div class='highlight'><pre> <span class="hljs-keyword">var</span> emitter = <span class="hljs-keyword">new</span> events.EventEmitter();</pre></div>
<h4 id="spawning-the-child-process">Spawning the child process</h4>
<p>Spawn a child process with the <code>detached</code> option makes it
the leader of its own process group. This allows us to
terminate all its descendant processes (if we need to) by running
<code>kill -$PGID</code> (negation of the child’s process group id).</p>
<div class='highlight'><pre> <span class="hljs-keyword">var</span> child;
<span class="hljs-keyword">var</span> hasExited = <span class="hljs-literal">false</span>, stdoutEnded = <span class="hljs-literal">false</span>, stderrEnded = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">var</span> spawnOpt = {
detached: opt.detached,
shell: opt.shell,
cwd: opt.cwd,
stdio: <span class="hljs-string">'pipe'</span>,
env: opt.env,
};</pre></div>
<p>Use <code>respawn.spawn()</code> to spawn the child process.</p>
<div class='highlight'><pre> child = spawn(commandLine, spawnOpt);
child.stdout.setEncoding(opt.stringEncoding);
child.stderr.setEncoding(opt.stringEncoding);</pre></div>
<p>Node 4 (LTS) and below: no setDefaultEncoding() function.</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (child.stdin.setDefaultEncoding) {
child.stdin.setDefaultEncoding(opt.stringEncoding);
}
<span class="hljs-keyword">if</span> (LEGACY_NODE) {</pre></div>
<p>Node 4 (LTS) and below: <code>child.pid</code> is not immediately populated, and
<code>child_process.spawnSync</code> does not support the <code>detached</code> option. So
the best we can do if <code>child.pid</code> is undefined is some exponential
backoff polling that is not guaranteed to succeed.</p>
<div class='highlight'><pre> <span class="hljs-keyword">var</span> pollIntervalMs = <span class="hljs-number">1</span>;
(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">pollFn</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-keyword">if</span> (child.pid) {</pre></div>
<p>Successfully have child’s pid</p>
<div class='highlight'><pre> emitter.pid = child.pid;
<span class="hljs-keyword">if</span> (opt.killOnExit) {
CLEANUP_PROCESSES[child.pid] = <span class="hljs-literal">true</span>;
}
} <span class="hljs-keyword">else</span> {
<span class="hljs-keyword">if</span> (pollIntervalMs < <span class="hljs-number">100</span>) {
setTimeout(pollFn, pollIntervalMs);
pollIntervalMs *= <span class="hljs-number">2</span>;
}
}
})();
} <span class="hljs-keyword">else</span> {</pre></div>
<p>Node 5+ natively returns child.pid set.</p>
<div class='highlight'><pre> emitter.pid = child.pid;
<span class="hljs-keyword">if</span> (opt.killOnExit) {
CLEANUP_PROCESSES[child.pid] = <span class="hljs-literal">true</span>;
}
}
} <span class="hljs-keyword">catch</span>(e) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(fmt(
<span class="hljs-string">'superchild: cannot spawn() child process, reason=%s, stack=%s'</span>,
e.message, e.stack));
}</pre></div>
<h4 id="reading-from-the-child">Reading from the child</h4>
<p>Read from the child process by parsing out complete lines and
line-delimited JSON objects and arrays from its <code>stdout</code> stream using
<code>JSONSieve</code>.</p>
<div class='highlight'><pre> <span class="hljs-keyword">var</span> sieve = <span class="hljs-keyword">new</span> JSONSieve();
<span class="hljs-keyword">var</span> sieveClosed = <span class="hljs-literal">false</span>;
child.stdout.on(<span class="hljs-string">'data'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">dataStr</span>) </span>{
sieve.observe(dataStr);
});
sieve.on(<span class="hljs-string">'json_object'</span>, emitter.emit.bind(emitter, <span class="hljs-string">'json_object'</span>));
sieve.on(<span class="hljs-string">'json_array'</span>, emitter.emit.bind(emitter, <span class="hljs-string">'json_array'</span>));
sieve.on(<span class="hljs-string">'stdout_line'</span>, emitter.emit.bind(emitter, <span class="hljs-string">'stdout_line'</span>));
child.stderr.on(<span class="hljs-string">'data'</span>, emitter.emit.bind(emitter, <span class="hljs-string">'stderr_data'</span>));
child.stdout.on(<span class="hljs-string">'data'</span>, emitter.emit.bind(emitter, <span class="hljs-string">'stdout'</span>));</pre></div>
<h4 id="writing-to-the-child-send-">Writing to the child: <code>send()</code></h4>
<div class='highlight'><pre> emitter.send = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">jsonVal</span>) </span>{
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">var</span> serData = <span class="hljs-built_in">JSON</span>.stringify(jsonVal) + <span class="hljs-string">'\n'</span>;
child.stdin.write(serData, opt.stringEncoding);
<span class="hljs-keyword">return</span> {
bytesWritten: serData.length,
prefix: serData.substr(<span class="hljs-number">0</span>, <span class="hljs-number">100</span>),
};
} <span class="hljs-keyword">catch</span>(e) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Unable to write to process: '</span> + e.message);
}
};</pre></div>
<h4 id="terminating-the-child-close-">Terminating the child: <code>close()</code></h4>
<p>Kill the child’s process group gracefully. This involves the following
steps:</p>
<ol>
<li>Send a <code>SIGTERM</code> signal to the child process group, allowing all
processes in the group to die gracefully.</li>
<li>Wait for <code>opt.cleanupTimeoutMs</code> milliseconds.</li>
<li>If the child process has not exited, send a <code>SIGKILL</code> to the
process group.</li>
</ol>
<p>This method is a public method that is offered
via the return value of the <code>superchild()</code> call.</p>
<p>Note that we wait for the proxy emitter’s <code>exit</code> event before invoking
the callback rather than the underlying node.js child process’s <code>exit</code>
event. This is in order to wait for all output streams to flush before
actually emitting the <code>exit</code> event, which is not guaranteed with
node’s underlying child_process implementation.</p>
<div class='highlight'><pre> emitter.close = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">cb</span>) </span>{
cb = cb || <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{};
<span class="hljs-keyword">if</span> (!child || !child.pid) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'superchild: child is not running.'</span>);
<span class="hljs-keyword">var</span> termGroupCmdLine = fmt(<span class="hljs-string">'/bin/kill -s SIGTERM -- -%d'</span>, child.pid);
<span class="hljs-keyword">var</span> killGroupCmdLine = fmt(<span class="hljs-string">'/bin/kill -s SIGKILL -- -%d'</span>, child.pid);
spawnSync(termGroupCmdLine, {shell: <span class="hljs-literal">true</span>, stdio: <span class="hljs-string">'inherit'</span>});
setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-keyword">if</span> (!sieveClosed) {
spawnSync(killGroupCmdLine, {shell: <span class="hljs-literal">true</span>, stdio: <span class="hljs-string">'inherit'</span>});
}
}, opt.cleanupTimeoutMs);
emitter.once(<span class="hljs-string">'exit'</span>, cb);
};</pre></div>
<h4 id="cleaning-up-the-child-process">Cleaning up the child process</h4>
<p>If the <code>killOnExit</code> option is specified, then when our own
current process exits, kill the child’s process group using
the POSIX <code>kill</code> command with the child’s process group ID,
which is the negation of its process id, since it is the leader
of its own process group.</p>
<p>If the child exits, then we need to wait for its streams to
close as well, and then flush the JSON sieve before emitting
the ‘exit’ event through the proxy emitter.</p>
<div class='highlight'><pre> <span class="hljs-keyword">var</span> exitCode, exitSignal;
<span class="hljs-keyword">var</span> cleanupFn = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-keyword">if</span> (hasExited && stdoutEnded && stderrEnded && !sieveClosed) {
sieve.close(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
emitter.emit(<span class="hljs-string">'exit'</span>, exitCode, exitSignal);
emitter.removeAllListeners();
});
sieveClosed = <span class="hljs-literal">true</span>;
}
};
<span class="hljs-keyword">var</span> onStdoutEnd = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{ stdoutEnded = <span class="hljs-literal">true</span>; cleanupFn(); };
<span class="hljs-keyword">var</span> onStderrEnd = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{ stderrEnded = <span class="hljs-literal">true</span>; cleanupFn(); };
child.stdout.once(<span class="hljs-string">'close'</span>, onStdoutEnd);
child.stderr.once(<span class="hljs-string">'close'</span>, onStderrEnd);
child.once(<span class="hljs-string">'exit'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">code, signal</span>) </span>{
hasExited = <span class="hljs-literal">true</span>;
exitCode = code;
exitSignal = signal;
<span class="hljs-keyword">delete</span> CLEANUP_PROCESSES[child.pid];
cleanupFn();
});</pre></div>
<h4 id="return-value">Return value</h4>
<p>The <code>superchild()</code> call returns a proxy <code>EventEmitter</code> with two
additional methods that are used to control the child process:
<code>close()</code> and <code>send()</code>.</p>
<div class='highlight'><pre> <span class="hljs-keyword">return</span> emitter;
}
process.once(<span class="hljs-string">'exit'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-built_in">Object</span>.keys(CLEANUP_PROCESSES).forEach(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">pid</span>) </span>{
<span class="hljs-keyword">var</span> killCmdLine = fmt(<span class="hljs-string">'/bin/kill -s SIGTERM -- -%d'</span>, pid);
spawnSync(killCmdLine, {stdio: <span class="hljs-string">'inherit'</span>});
});
});
<span class="hljs-built_in">module</span>.exports = superchild;</pre></div>
<h4 id="helper-utilities">Helper utilities</h4>
<ul>
<li><a href="http://mayanklahiri.github.io/node-superchild/json-sieve.html"><code>Class JSONSieve</code></a>,
parses a readable stream into JSON objects and arrays, and stdout lines.</li>
<li><a href="http://mayanklahiri.github.io/node-superchild/unlogger.html"><code>function unlogger()</code></a>,
an example of establishing bi-directional communication between a parent
and child that can be easily ported to many other languages.</li>
</ul>
<div class='highlight'><pre>superchild.JSONSieve = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./json-sieve'</span>);
superchild.unlogger = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./unlogger'</span>);</pre></div>
<div class="fleur">h</div>
</div>
</div>
</body>
</html>