/*! * Cluster - repl * Copyright (c) 2011 LearnBoost * MIT Licensed */ /** * Module dependencies. */ var net = require('net') , repl = require('repl'); /** * Enable REPL with all arguments passed to `net.Server#listen()`. * * Examples: * * cluster(server) * .use(cluster.stats()) * .use(cluster.repl('/var/run/cluster')) * .listen(); * * In the terminal: * * $ sudo telnet /var/run/cluster * * @return {Function} * @api public */ exports = module.exports = function(){ var args = arguments; if (!args.length) throw new Error('repl() plugin requires port/host or path'); return function(master){ var server , sockets = []; // start repl function start(){ // TCP or unix-domain socket repl server = net.createServer(function(sock){ sockets.push(sock); var ctx = repl.start('cluster> ', sock).context; master.emit('repl socket', sock); // augment socket to provide some formatting methods sock.title = function(str){ this.write('\n \033[36m' + str + '\033[0m\n'); } sock.row = function(key, val){ this.write(' \033[90m' + key + ':\033[0m ' + val + '\n'); } // merge commands into context // executing in context of master Object.keys(exports).forEach(function(cmd){ ctx[cmd] = function(){ var args = Array.prototype.slice.call(arguments); args.unshift(master, sock); return exports[cmd].apply(master, args); }; }); }); // Apply all arguments given server.listen.apply(server, args); } // initial master starts immediately // replacements starts when the previous // has closed master.on(master.isChild ? 'restart' : 'start', start); // restart notification master.on('restarting', function(){ sockets.forEach(function(sock){ if (sock.fd) { sock.write('\n\033[33mrestarting\033[0m - closing connection soon\n'); } }); }); // close master.on('close', function(){ sockets.forEach(function(sock){ sock.fd && sock.end(); }); if (server && server.fd) server.close(); }); } }; /** * Define function `name`, with the given callback * `fn(master, sock, ...)` and `description`. * * @param {String} name * @param {Function} fn * @param {String} desc * @return {Object} exports for chaining * @api public */ var define = exports.define = function(name, fn, desc){ (exports[name] = fn).description = desc; return exports; }; /** * Display commmand help. */ define('help', function(master, sock){ sock.title('Commands'); Object.keys(exports).forEach(function(cmd){ if ('define' == cmd) return; var fn = exports[cmd] , params = fn.toString().match(/^function +\((.*?)\)/)[1] , params = params.split(/ *, */).slice(2); sock.row( cmd + '(' + params.join(', ') + ')' , fn.description); }); sock.write('\n'); }, 'Display help information'); /** * Spawn `n` additional workers with the given `signal`. */ define('spawn', function(master, sock, n, signal){ n = n || 1; if (n < 0) { n = Math.abs(n); sock.write('removing ' + n + ' worker' + (n > 1 ? 's' : '') + ' with ' + (signal || 'SIGQUIT') + '\n'); master.remove(n, signal); } else { sock.write('spawning ' + n + ' worker' + (n > 1 ? 's' : '') + '\n'); master.spawn(n); } }, 'Spawn one or more additional workers, or remove one or more'); /** * Output process ids. */ define('pids', function(master, sock){ sock.title('pids'); sock.row('master', process.pid); master.children.forEach(function(worker){ sock.row('worker #' + worker.id, worker.proc.pid); }); sock.write('\n'); }, 'Output process ids'); /** * Kill the given worker by `id` and `signal`. */ define('kill', function(master, sock, id, signal){ var worker = master.children[id]; if (worker) { worker.proc.kill(signal); sock.write('sent \033[36m' + (signal || 'SIGTERM') + '\033[0m to worker #' + id + '\n'); } else { sock.write('invalid worker id\n'); } }, 'Send signal or SIGTERM to the given worker'); /** * Gracefully shutdown. */ define('shutdown', function(master, sock){ master.close(); }, 'Gracefully shutdown server'); /** * Hard shutdown. */ define('stop', function(master, sock){ master.destroy(); }, 'Hard shutdown'); /** * Gracefully restart all workers. */ define('restart', function(master, sock){ master.restart(); }, 'Gracefully restart all workers');