fcoin/lib/workers/child.js
Christopher Jeffrey f591e577f1
workers: minor.
2017-07-17 14:26:41 -07:00

188 lines
3.5 KiB
JavaScript

/*!
* child.js - child processes for bcoin
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
const EventEmitter = require('events');
const path = require('path');
const cp = require('child_process');
const util = require('../utils/util');
const children = new Set();
let exitBound = false;
/**
* Represents a child process.
* @alias module:workers.Child
* @constructor
* @param {String} file
* @param {Object} env
*/
function Child(file, env) {
if (!(this instanceof Child))
return new Child(file, env);
EventEmitter.call(this);
children.add(this);
bindExit();
this.init(file, env);
}
util.inherits(Child, EventEmitter);
/**
* Test whether child process support is available.
* @returns {Boolean}
*/
Child.hasSupport = function hasSupport() {
return true;
};
/**
* Initialize child process (node.js).
* @private
*/
Child.prototype.init = function init(file, env) {
let bin = process.argv[0];
let filename = path.resolve(__dirname, file);
let environ = Object.assign({}, process.env, env);
let options = { stdio: 'pipe', env: environ };
this.child = cp.spawn(bin, [filename], options);
this.child.unref();
this.child.stdin.unref();
this.child.stdout.unref();
this.child.stderr.unref();
this.child.on('error', (err) => {
this.emit('error', err);
});
this.child.on('exit', (code, signal) => {
children.delete(this);
this.emit('exit', code == null ? -1 : code, signal);
});
this.child.on('close', () => {
children.delete(this);
this.emit('exit', -1, null);
});
this.child.stdin.on('error', (err) => {
this.emit('error', err);
});
this.child.stdout.on('error', (err) => {
this.emit('error', err);
});
this.child.stderr.on('error', (err) => {
this.emit('error', err);
});
this.child.stdout.on('data', (data) => {
this.emit('data', data);
});
};
/**
* Send data to child process.
* @param {Buffer} data
* @returns {Boolean}
*/
Child.prototype.write = function write(data) {
return this.child.stdin.write(data);
};
/**
* Destroy the child process.
*/
Child.prototype.destroy = function destroy() {
this.child.kill('SIGTERM');
};
/*
* Helpers
*/
function bindExit() {
let onSighup, onSigint, onSigterm, onError;
if (exitBound)
return;
exitBound = true;
onSighup = () => {
process.exit(1 | 0x80);
};
onSigint = () => {
process.exit(2 | 0x80);
};
onSigterm = () => {
process.exit(15 | 0x80);
};
onError = (err) => {
if (err && err.stack)
console.error(err.stack + '');
else
console.error(err + '');
process.exit(1);
};
process.once('exit', () => {
for (let child of children)
child.destroy();
});
if (process.listenerCount('SIGHUP') === 0)
process.once('SIGHUP', onSighup);
if (process.listenerCount('SIGINT') === 0)
process.once('SIGINT', onSigint);
if (process.listenerCount('SIGTERM') === 0)
process.once('SIGTERM', onSigterm);
if (process.listenerCount('uncaughtException') === 0)
process.once('uncaughtException', onError);
process.on('newListener', (name) => {
switch (name) {
case 'SIGHUP':
process.removeListener(name, onSighup);
break;
case 'SIGINT':
process.removeListener(name, onSigint);
break;
case 'SIGTERM':
process.removeListener(name, onSigterm);
break;
case 'uncaughtException':
process.removeListener(name, onError);
break;
}
});
}
/*
* Expose
*/
module.exports = Child;