the-shepherd
Version:
Control a herd of wild processes.
287 lines (262 loc) • 8.95 kB
JavaScript
// Generated by CoffeeScript 2.5.1
(function() {
//!/usr/bin/env coffee
// this is the master process that maintains the herd of processes
var $, Actions, Chalk, ChildProcess, Fs, Groups, Net, Output, SlimProcess, _c, basePath, carefulUnlink, cmd, configFile, die, doStart, doStatus, doStop, echo, exists, exit_soon, expandPath, handleMessage, outputFile, pidFile, readConfig, readPid, runDaemon, socketFile, started, verbose, warn;
({$, cmd, echo, warn, verbose} = require('../common'));
Fs = require('fs');
Net = require('net');
Chalk = require('chalk');
Output = require('./output');
({Groups} = require('./groups'));
({Actions} = require('../actions'));
({pidFile, socketFile, outputFile, configFile, basePath, expandPath, readPid, carefulUnlink} = require('../files'));
({readConfig} = require('../util/config'));
ChildProcess = require('child_process');
SlimProcess = require('../util/process-slim');
exit_soon = (n, ms = 200) => {
return setTimeout((() => {
verbose(`Calling process.exit(${n}), after delay of ${ms}ms`);
return process.exit(n);
}), ms);
};
die = function(...msg) {
console.error(...msg);
return exit_soon(1);
};
handleMessage = function(msg, client, cb) {
var g, i, ref;
if ('auto' in msg) {
if (Groups.has(msg.auto)) {
msg.g = msg.auto;
} else {
[g, i] = msg.auto.split('-');
if (Groups.has(g)) {
msg.i = g + "-" + parseInt(i, 10);
} else {
msg.g = msg.auto;
}
}
}
return (ref = Actions[msg.c]) != null ? typeof ref.onMessage === "function" ? ref.onMessage(msg, client, cb) : void 0 : void 0;
};
exists = function(path) {
var stat;
try {
return (stat = Fs.statSync(expandPath(path))).isFile() || stat.isSocket();
} catch (error) {
return false;
}
};
doStop = function(exit, client, cb) {
var err, pid;
cb || (cb = function() {});
verbose(`Daemon.doStop(exit=${exit})`);
if (!(pid = readPid())) {
cb(`Expected PID file: ${pidFile}`, false);
if (exit) {
return exit_soon(0);
}
} else {
verbose("daemon/index Sending stop message to all groups...");
try {
Actions.stop.onMessage({}, null, function(err) { // send a stop command to all running instances
verbose("daemon/index All stop messages returned...");
// then kill the pid from the pid file
return SlimProcess.killAllChildren(pid, "SIGTERM", function(err) {
if (err) {
warn("daemon/index Error from killAllChildren", err);
}
return carefulUnlink(pidFile, function(err) {
if (err) {
warn(`daemon/index Error while unlinking PID file (${pidFile}):`, err);
}
return carefulUnlink(socketFile, function(err) {
if (err) {
warn(`daemon/index Error while unlinking unix socket (${socketFile}):`, err);
}
cb(null, true);
if (exit) {
return exit_soon(0);
}
});
});
});
});
} catch (error) {
err = error;
cb(err);
}
}
return null;
};
doStatus = function() {
$.log("Socket:", socketFile, exists(socketFile) ? Chalk.green("(exists)") : Chalk.yellow("(does not exist)"));
return $.log("PID File:", pidFile, exists(pidFile) ? Chalk.green("(exists)") : Chalk.yellow("(does not exist)"));
};
started = false;
runDaemon = () => { // in the foreground
var _pidFile, _socketFile;
if (!pidFile) {
return die("Daemon has no pidFile configured.");
}
if (!socketFile) {
return die("Daemon has no socketFile configured.");
}
_pidFile = expandPath(pidFile);
_socketFile = expandPath(socketFile);
if (exists(_pidFile)) {
return die("Already running as PID:" + readPid());
}
if (exists(_socketFile)) {
return die("Socket file still exists:" + socketFile);
}
return Fs.writeFile(_pidFile, String(process.pid), (err) => {
if (err) {
return die("Failed to write pid file:", err);
}
return Output.setOutput(outputFile, (err) => {
var j, len, ref, shutdown, sig, socket;
if (err) {
return die("Failed to set output file:", err);
}
echo("Opening master socket...", socketFile);
socket = Net.Server().listen({
path: _socketFile
});
socket.on('error', function(err) {
echo("Failed to open local socket:", $.debugStack(err));
return exit_soon(1);
});
socket.on('connection', function(client) {
client.on('error', function(err) {
return warn("client error:", err);
});
return client.on('data', function(msg) {
var start;
start = Date.now();
msg = $.TNET.parse(msg.toString());
return handleMessage(msg, client, (err, acted) => {
var _msg, k, v;
if (err) {
try {
warn(msg, "caused", $.debugStack(err));
return client != null ? client.write($.TNET.stringify($.debugStack(err))) : void 0;
} catch (error) {}
}
_msg = Object.create(null);
for (k in msg) {
v = msg[k];
if (v != null) {
_msg[k] = v;
}
}
return echo("Command handled:", _msg, "in", Date.now() - start, "ms");
});
});
});
shutdown = function(signal) {
return function() {
var progress;
echo(`${signal}: Shutting down from signal...`);
verbose(`${signal}: Closing master socket...`);
try {
socket.close();
} catch (error) {
err = error;
warn("Failed to close socket: ", err);
}
verbose(`${signal}: Stopping all groups...`);
progress = $.Progress(Groups.size + 1);
Groups.forEach(function(group) {
return group.stop(function(err) {
if (err) {
return progress.reject(err);
} else {
return progress.finish(1);
}
});
});
progress.finish(1).wait(function() {
verbose(`${signal}: Unlinking PID file...`);
try {
Fs.unlinkSync(_pidFile);
} catch (error) {
err = error;
warn(`Failed to unlink pid file (${_pidFile}):`, err);
}
if (signal !== 'exit') {
verbose(`${signal}: Scheduling delayed exit...`);
return exit_soon(0, 2000);
}
});
return null;
};
};
ref = ['SIGINT', 'SIGTERM', 'exit'];
for (j = 0, len = ref.length; j < len; j++) {
sig = ref[j];
verbose(`Signal ${sig}: attaching handler...`);
process.on(sig, shutdown(sig));
}
readConfig();
return started = true;
});
});
};
doStart = (exit) => { // launch the daemon in the background and exit
var args, child, devNull, stdio;
echo("Starting daemon...");
cmd = process.argv[0];
args = [process.argv[1].replace("client/index", "daemon/index"), "daemon", `--base \"${basePath.replace(/\/.shep$/, '')}\"`, (cmd.verbose ? "--verbose" : ""), (cmd.quiet ? "--quiet" : "")];
devNull = Fs.openSync("/dev/null", 'a+');
stdio = [
devNull,
devNull,
process.stderr // only let stderr pass through
];
child = ChildProcess.spawn(cmd, args, {
detached: true,
shell: false,
stdio: stdio
});
child.on('error', function(err) {
return console.error("Child exec error:", err);
});
child.unref();
if (exit) {
exit_soon(0);
}
return true;
};
if (require.main === module) {
switch (_c = cmd._[0]) {
case "stop":
doStop(true, null, function(err, acted) {
return echo("Stopped");
});
break;
case "start":
doStart(true);
break;
case "daemon":
runDaemon();
break;
case "restart":
doStop(false, null, function(err, acted) {
return doStart(true);
});
break;
case "status":
doStatus();
break;
default:
console.log("Unknown usage:", cmd);
console.log("Usage: shepd <command>");
console.log("Commands: start stop restart status help");
exit_soon(0);
}
}
true;
Object.assign(module.exports, {doStart, doStop, doStatus});
}).call(this);