UNPKG

superchild

Version:

A smarter replacement for node.js's child_process module.

560 lines (425 loc) 28.4 kB
<!DOCTYPE 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:&amp;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> -&gt; <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 &lt;mayank@iceroad.io&gt; */</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 &lt; <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 &lt; <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 &amp;&amp; stdoutEnded &amp;&amp; stderrEnded &amp;&amp; !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>