TheBrainFamily/chimpy

View on GitHub
src/lib/process-helper.js

Summary

Maintainability
A
3 hrs
Test Coverage
var cp = require('child_process'),
  log = require('./log');

module.exports = {

  start: function (options, callback) {

    var child = this.spawn(options);

    if (options.waitForMessage) {
      this.waitForMessage(options, child, callback);
    } else {
      callback();
    }

    return child;
  },

  spawn: function (options) {
    log.debug('[chimp][' + options.prefix + ']', 'starting process');

    var child = cp.spawn(options.bin, options.args);

    this.logOutputs(options.prefix, child);

    return child;
  },

  logOutputs: function (prefix, child) {
    child.stdout.on('data', function (data) {
      log.debug('[chimp][' + prefix + '.stdout]', data.toString());
    });
    child.stderr.on('data', function (data) {
      log.debug('[chimp][' + prefix + '.stderr]', data.toString());
    });
  },

  waitForMessage: function (options, child, callback) {
    child.stderr.on('data', onDataStdErr);
    child.stdout.on('data', onDataStdOut);

    function onDataStdErr(data) {
      onData(data, () => {
        child.stderr.removeListener('data', onDataStdErr);
      });
    }

    function onDataStdOut(data) {
      onData(data, () => {
        child.stdout.removeListener('data', onDataStdOut);
      });
    }

    function onData(data, removeListener) {
      if (data.toString().match(options.waitForMessage)) {
        removeListener();
        log.debug('[chimp][' + options.prefix + ']', 'started successfully');
        return callback();
      }
      if (data.toString().match(options.errorMessage)) {
        log.error('[chimp][' + options.prefix + ']', 'failed to start');
        log.error(data.toString());
        callback(data.toString());
      }
    }
  },

  kill: function (options, callback) {

    log.debug('[chimp][' + options.prefix + ']', 'kill called on ' + options.prefix + ' process with pid', options.child.pid);

    options.signal = options.signal || 'SIGTERM';

    try {
      log.debug('[chimp][' + options.prefix + ']', 'checking if process exists');
      process.kill(options.child.pid, 0);
      log.debug('[chimp][' + options.prefix + ']', options.prefix + ' process exists, killing it with', options.signal);
      process.kill(options.child.pid, options.signal);
    } catch (e) {
      log.debug('[chimp][' + options.prefix + ']', options.prefix + ' process does not exists, ignoring');
      options.child = null;
      return callback();
    }

    var delay = 300,
      totalRetries = 10,
      retries = totalRetries * (1000 / delay),
      attempt = 0;

    var waitForProcessToDie = setInterval(function () {
      try {
        if (retries-- < 0) {
          throw new Error('Process took too long to die');
        }
        log.debug('[chimp][' + options.prefix + ']', 'waiting for process to die (' + attempt++ + '/' + totalRetries + ')');
        process.kill(options.child.pid, 0);
      } catch (e) {
        clearInterval(waitForProcessToDie);
        if (e.code === 'ESRCH') {
          log.debug('[chimp][' + options.prefix + ']', 'process is dead');
          options.child = null;
          callback();
        } else {
          callback(e);
        }
      }
    }, delay);
  }

};