UNPKG

naught-node10

Version:

zero downtime deployment for your node.js server

263 lines (238 loc) 6.81 kB
var fs = require('fs'); var mkdirp = require('mkdirp'); var path = require('path'); var spawn = require('child_process').spawn; var net = require('net'); var assert = require('assert'); var Pend = require('pend'); var jsonSocket = require('./json_socket'); var createLog = require('./log').create; var own = {}.hasOwnProperty; exports.start = startDaemon; function startDaemon(argv) { var workerCount = parseInt(argv.shift(), 10); var socketPath = argv.shift(); var logNaughtPath = argv.shift(); var logStderrPath = argv.shift(); var logStdoutPath = argv.shift(); var maxLogSize = parseInt(argv.shift(), 10); var script = argv.shift(); var nodeArgsStr = argv.shift(); var pidFile = argv.shift(); var naughtLog = null; var stderrLog = null; var stdoutLog = null; var naughtLogBehavior = null; var stderrBehavior = null; var stdoutBehavior = null; var socket = null; var master = null; var server = null; fs.writeFile(pidFile, process.pid, function(){ createLogsAndIpcServer(function(err) { if (err) { processSend({event: 'Error', value: err.message}); return; } process.on('SIGHUP', function() { handleSocketMessage({ action: 'NaughtDeploy', newWorkerCount: 0, environment: {}, timeout: null, cwd: null, }); }); process.on('SIGTERM', function() { handleSocketMessage({ action: 'NaughtShutdown' }); }); process.on('SIGINT', function() { handleSocketMessage({ action: 'NaughtShutdown' }); }); processSend({event: 'IpcListening'}); spawnMaster(); }); }); function maybeCreateLog(logPath, cb) { // special case /dev/null - disable logging altogether if (logPath === '/dev/null') { cb(null, { behavior: 'ignore', log: null, }); } else if (logPath === '-') { cb(null, { behavior: 'inherit', log: null, }); } else { createLog(logPath, maxLogSize, function(err, logStream) { if (err) return cb(err); cb(null, { behavior: 'pipe', log: logStream, }); }); } } function createLogs(cb) { var pend = new Pend(); pend.go(function(cb) { maybeCreateLog(logNaughtPath, function(err, results) { naughtLog = results.log; naughtLogBehavior = results.behavior; cb(err); }); }); pend.go(function(cb) { maybeCreateLog(logStderrPath, function(err, results) { stderrLog = results.log; stderrBehavior = results.behavior; cb(err); }); }); pend.go(function(cb) { maybeCreateLog(logStdoutPath, function(err, results) { stdoutLog = results.log; stdoutBehavior = results.behavior; cb(err); }); }); pend.wait(function(err) { if (err) return cb(err); if (naughtLogBehavior === 'inherit') { naughtLog = process.stderr; } if (stderrBehavior === 'pipe') { stderrLog.on('error', function(err) { log("Error writing to " + logStderrPath + ": " + err.stack + "\n"); }); } if (stdoutBehavior === 'pipe') { stdoutLog.on('error', function(err) { log("Error writing to " + logStdoutPath + ": " + err.stack + "\n"); }); } if (naughtLogBehavior === 'pipe') { naughtLog.on('error', function(err){ process.stderr.write("Error writing to " + logNaughtPath + ": " + err.stack + "\n"); }); } cb(); }); } function log(str) { if (naughtLog) naughtLog.write(str); } function workerCountsFromMsg(counts) { return "booting: " + counts.booting + ", online: " + counts.online + ", dying: " + counts.dying + ", new_online: " + counts.new_online; } function onMessage(message) { if (naughtLog) { var str = message.event + "."; if (message.count) str += " " + workerCountsFromMsg(message.count); naughtLog.write(str + "\n"); } if (socket) jsonSocket.send(socket, message); processSend(message); } function createLogsAndIpcServer(cb) { var pend = new Pend(); createLogs(pend.hold()); mkdirp(path.dirname(socketPath), pend.hold()); pend.wait(function(err) { if (err) return cb(err); server = net.createServer(function(newSocket){ if (socket != null) { log("Warning: Only one connection to daemon allowed. Terminating old connection.\n"); socket.destroy(); } socket = newSocket; socket.on('error', function(err){ log("Error: ipc channel socket: " + err.stack + "\n"); }); socket.once('end', function(){ socket = null; }); jsonSocket.listen(socket, function(msg){ var response = handleSocketMessage(msg); if (response) { jsonSocket.send(response); } }); }); server.listen(socketPath, cb); }); } function spawnMaster() { var nodeArgs = splitCmdLine(nodeArgsStr); var stdoutValue = (stdoutBehavior === 'inherit') ? process.stdout : stdoutBehavior; var stderrValue = (stderrBehavior === 'inherit') ? process.stderr : stderrBehavior; master = spawn(process.execPath, nodeArgs.concat([path.join(__dirname, "master.js"), workerCount, script]).concat(argv), { env: process.env, stdio: [process.stdin, stdoutValue, stderrValue, 'ipc'], cwd: process.cwd(), }); master.on('message', onMessage); if (stdoutBehavior === 'pipe') { master.stdout.pipe(stdoutLog, {end: false}); } if (stderrBehavior === 'pipe') { master.stderr.pipe(stderrLog, {end: false}); } master.on('close', function(){ onMessage({ event: 'Shutdown', count: { booting: 0, online: 0, dying: 0, new_online: 0 } }); try{ fs.unlinkSync(pidFile); } catch (e) {} server.close(); }); } function handleSocketMessage(msg) { if (master != null) { if (msg.action === 'NaughtDeploy') { extend(process.env, msg.environment); } master.send(msg); return null; } else { processSend({event: 'Error', value: 'StillBooting'}); return { event: 'ErrorStillBooting', }; } } } function splitCmdLine(str) { if (str.length === 0) { return []; } else { return str.split(/\s+/); } } function extend(obj, src) { for (var key in src) { if (own.call(src, key)) obj[key] = src[key]; } return obj; } function processSend(msg) { try { if (process.connected) { process.send(msg); } } catch (err) { // ignore } }