UNPKG

mushroom

Version:

Mushroom sprouts a never-ending set of child processes which can report statistics back to the host. Like multi-process forever.

274 lines (235 loc) 6.15 kB
module.exports = (function() { var childProcess = require("child_process"); var http = require("http"); var url = require("url"); var EventEmitter = require("events").EventEmitter; //a more powerful _.extend function extend(obj) { var recurse = arguments.callee; Array.prototype.slice.call(arguments, 1).forEach(function(source) { for(var prop in source) { if(source[prop] instanceof Array) obj[prop] = ((obj[prop] instanceof Array)? obj[prop] : []).concat(source[prop]); else if((typeof(obj[prop]) == "object") && (typeof(source[prop]) == "object")) recurse(obj[prop], source[prop]); else obj[prop] = source[prop]; } }); return(obj); }; var sporeSingleton; Spore.prototype = extend(new EventEmitter(), { port: null, __tally: null, __heartbeat: null, tally: function(channel, count, metadata) { channel = channel || "general"; var t = (this.__tally[channel] || (this.__tally[channel] = {count: 0, meta: []})); t.count += count; if(metadata) t.meta.push({time: Date.now(), meta: metadata}); } }); function Spore() { var ths = (this instanceof arguments.callee)? this : new arguments.callee(); //force ctor call ths.port = process.env["MUSHROOM_PORT"]; ths.serverPort = process.env["MUSHROOM_SERVER_PORT"]; ths.__tally = {}; ths.__heartbeat = setInterval(function() { http.request( { method: "PUT", host: "localhost", port: ths.serverPort, path: "/checkin/" + ths.port }, function(response) { //if(response.statusCode != 200) //console.log("Unexpect server status: ", response.statusCode); var json = ""; response.setEncoding("utf8"); response.on("data", function(d) { json += d; }).on("end", function() { try { json = JSON.parse(json); if(json.cmd) console.log(json); } catch(e) { //console.log("Malformed JSON: >>" + json + "<<"); } }); }).on("error", function(err) { //console.log("Heartbeat failed: ", err); }).end(JSON.stringify( { //send these things to the master time: Date.now(), tally: ths.__tally, mem: process.memoryUsage() })); ths.__tally = {}; }, 3000); return(ths); } SporeHandle.prototype = { __restartWhenDied: null, __restartDelay: null, __spore: null, stderr: null, restart: function() { if(this.__spore) { this.__spore.kill(); this.__spore = undefined; } }, stop: function() { if(this.__spore) { this.__restartWhenDied = false; this.__spore.kill(); this.__spore = undefined; } } }; function SporeHandle(spore) { this.stderr = ""; this.__spore = spore; this.__restartWhenDied = true; this.__restartDelay = 1000; } var globalSporeHandles = []; var globalExceptionListener = false; Shroom.prototype = extend(new EventEmitter(), { __serverPort: null, __options: null, __spres: null, spawn: function(processArgs, port) { this.__ensureServer(); var ths = this; var spore = childProcess.spawn(process.execPath, processArgs, { env: { "MUSHROOM_SERVER_PORT": this.__serverPort, "MUSHROOM_PORT": port } }); var sporeHandle = this.__spores[port] || (this.__spores[port] = new SporeHandle(spore)); spore.stdout.setEncoding("utf8"); spore.stderr.setEncoding("utf8"); spore.stdout.on("data", function(d) { //console.log("stdout: ", d); ths.emit("stdout", {source: sporeHandle, port: port, data: d}); }); spore.stderr.on("data", function(d) { //console.log("stderr: ", d); sporeHandle.stderr = (sporeHandle.stderr + d).substr(-((this.__options && this.__options.scrollbackLength) || 4000)); ths.emit("stderr", {source: sporeHandle, port: port, data: d}); }); spore.on("exit", function(code) { //console.log("Spore " + port + " exited with: ", code); ths.emit("exit", {source: sporeHandle, port: port, code: code}); if(sporeHandle.__restartWhenDied) { console.log("Respawning spore " + port + " in a moment..."); setTimeout(function(){ths.spawn(processArgs, port)}, sporeHandle.__restartDelay); //@@respawn timeout } }); globalSporeHandles.push(sporeHandle); return(sporeHandle); }, __ensureServer: function() { var ths = this; if(!this.__server) { this.__server = http.createServer(function(request, response) { var args = url.parse(request.url, true); request.method = request.method.toLowerCase(); request.args = {query: args.query}; var urlParts = args.pathname.split("/").filter(function(p){return(p != "");}); var finish = function(result, httpCode, headers) { response.writeHead(httpCode || 200); response.end(JSON.stringify(result)); }; switch(urlParts[0]) { case "checkin": ths.sporeCheckin(request, response, {url: {sporeID: urlParts[1]}}, finish); break; default: finish({}, 404); break; } }); this.__server.listen(this.__serverPort); } }, sporeCheckin: function(request, response, args, callback) { var ths = this; var data = ""; request.setEncoding("utf8"); request.on("data", function(d) { data += d; }).on("end", function() { //console.log("RX from " + args.url.sporeID + ": ", data); callback({}); var message; try{message = JSON.parse(json);} catch(e){} var source = ths.__spores[args.url.sporeID]; ths.emit("data", {source: source, port: args.url.sporeID, message: message}); }); } }); function Shroom(options) { this.__options = options; this.__serverPort = (this.__options && this.__options.port) || 1985; this.__spores = {}; if(!globalExceptionListener) { globalExceptionListener = true; process.on("uncaughtException", function(e) { for(var i = 0; i < globalSporeHandles.length; i++) globalSporeHandles[i].stop(); throw e; }); } } return( { spore: function(){return(sporeSingleton || (sporeSingleton = new Spore()));}, shroom: Shroom }); })();