Skip to content
Browse files

flesh out ideas

  • Loading branch information...
1 parent 8fbaaa6 commit 43fd35de9c19a1bae0c0ed9b05053e2c1aba4f42 @andrewrk andrewrk committed Sep 13, 2012
Showing with 301 additions and 4 deletions.
  1. +4 −0 .gitignore
  2. +4 −0 .npmignore
  3. +19 −0 Cokefile
  4. +63 −0 README
  5. +0 −4 README.md
  6. +20 −0 package.json
  7. +118 −0 src/main.co
  8. +47 −0 src/wrapper.co
  9. +11 −0 test/simple/server.js
  10. +15 −0 test/simple/test.js
View
4 .gitignore
@@ -0,0 +1,4 @@
+# shared with .npmignore
+/node_modules
+# different from .npmignore
+/lib
View
4 .npmignore
@@ -0,0 +1,4 @@
+# shared with .gitignore
+/node_modules
+# different from .gitignore
+/src
View
19 Cokefile
@@ -0,0 +1,19 @@
+const {spawn} = require('child_process')
+
+const exec = !(cmd, args=[], opts={}, cb=->) ->
+ const bin = spawn(cmd, args, opts)
+ bin.stdout.pipe process.stdout
+ bin.stderr.pipe process.stderr
+ bin.on \exit, cb
+
+const build = !(w='') ->
+ exec 'coco', ["-#{w}bco", 'lib/', 'src/']
+
+const watch = !-> build('w')
+
+task \build "compile source files" !-> build()
+task \watch "compile source files and watch for changes" watch
+
+task \clean "delete generated files" !->
+ exec 'rm', ['-rf', 'lib']
+
View
63 README
@@ -0,0 +1,63 @@
+features:
+
+ * zero downtime code hot-swapping
+ - not done yet
+ * ability to change environment variables of workers with zero downtime
+ - not done yet
+ * resuscitation - when a worker dies it is restarted
+ - not done yet
+ * redirect worker stdout and stderr to rotating log files
+ - not done yet
+ * runs as daemon, providing ability to start and stop
+ - not done yet
+
+usage:
+
+ To use naught, your node.js server has 2 requirements.
+
+ 1. Once the server is fully booted and is readily accepting connections,
+
+ process.send('online');
+
+ 2. Listen to the 'shutdown' message and shutdown gracefully. This message
+ is emitted after there is already a newer instance of your server
+ online and taking care of business:
+
+ process.on('message', function(message) {
+ if (message === 'shutdown') {
+ performCleanup();
+ process.exit(0);
+ }
+ }
+
+ naught start [options] server.js [script-options]
+
+ Available options and their defaults:
+
+ --worker-count 1
+ --pidfile naught.pid
+ --log naught.log
+ --stdout stdout.log
+ --stderr stderr.log
+ --max-log-size 10485760
+ --cwd .
+
+ naught stop [pidfile.pid]
+
+ Stops the running server which created the pidfile.
+
+ naught deploy [options] [pidfile.pid]
+
+ Replaces workers with new workers using new code and the environment
+ variables from this command. Naught does this one worker at a time,
+ and waits to kill a worker until after the replacement is online.
+
+ naught deploy-abort [pidfile.pid]
+
+installation:
+
+ $ sudo npm install -g naught
+
+developing:
+
+ $ npm run dev
View
4 README.md
@@ -1,4 +0,0 @@
-naught
-======
-
-zero downtime deployment for your node.js server
View
20 package.json
@@ -0,0 +1,20 @@
+{
+ "name": "naught",
+ "version": "0.0.0",
+ "description": "zero downtime deployment for your node.js server",
+ "scripts": {
+ "dev": "npm install && coke watch"
+ },
+ "bin": {
+ "naught": "./lib/main.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/superjoe30/naught.git"
+ },
+ "author": "Andrew Kelley",
+ "license": "BSD",
+ "devDependencies": {
+ "coco": "~0.8.1"
+ }
+}
View
118 src/main.co
@@ -0,0 +1,118 @@
+`#!/usr/bin/env node`
+!function printUsage
+ console.log "Usage:"
+ for name, cmd in cmds
+ console.log "\n#{cmd.help}\n"
+
+!function startScript (options, script, argv)
+ {spawn} = require('child_process')
+ path = require('path')
+
+ log_naught = fs.createWriteStream options.log,
+ flags: 'a'
+ encoding: 'utf8'
+ log_stderr = fs.createWriteStream options.stderr,
+ flags: 'a'
+ encoding: 'utf8'
+ log_stdout = fs.createWriteStream options.stdout,
+ flags: 'a'
+ encoding: 'utf8'
+
+ !function log (str)
+ log_naught.write str
+ process.stdout.write str
+
+ child = spawn(process.execPath, [
+ path.join(__dirname, "wrapper.js"),
+ options.'worker-count',
+ options.cwd,
+ script,
+ ].concat(argv), {
+ env: process.env
+ stdio: ['inherit', 'pipe', 'pipe']
+ detached: true
+ cwd: options.cwd
+ })
+ child.stdout.pipe(log_stdout)
+ child.stderr.pipe(log_stderr)
+ fs.writeFile options.pidfile, child.pid.toString(), \utf8, !(err) ->
+ if err then log "Error: Unable to write pidfile to #{options.pidfile}.\n#{err.stack}\n"
+
+cmds =
+ start:
+ help: """
+ naught start [options] server.js [script-options]
+
+ Available options and their defaults:
+
+ --worker-count 1
+ --pidfile naught.pid
+ --log naught.log
+ --stdout stdout.log
+ --stderr stderr.log
+ --max-log-size 10485760
+ --cwd .
+ """
+ fn: (argv) ->
+ # parse options
+ options =
+ 'worker-count': '1'
+ 'pidfile': 'naught.pid'
+ 'log': 'naught.log'
+ 'stdout': 'stdout.log'
+ 'stderr': 'stderr.log'
+ 'max-log-size': '10485760'
+ 'cwd': process.cwd()
+ while argv.length
+ arg = argv.shift()
+ if arg.indexOf('--') is 0
+ options[arg.substring(2)] = argv.shift()
+ else
+ script = arg
+ break
+ if script?
+ options.'worker-count' = parseInt(options.'worker-count')
+ options.'max-log-size' = parseInt(options.'max-log-size')
+ startScript(options, script, argv)
+ true
+ else
+ false
+ stop:
+ help: """
+ naught stop [pidfile.pid]
+
+ Stops the running server which created the pidfile.
+ """
+ fn: (argv) -> false
+ deploy:
+ help: """
+ naught deploy [options] [pidfile.pid]
+
+ Replaces workers with new workers using new code and the environment
+ variables from this command. Naught does this one worker at a time,
+ and waits to kill a worker until after the replacement is online.
+ """
+ fn: (argv) -> false
+ 'deploy-abort':
+ help: """
+ naught deploy-abort [pidfile.pid]
+ """
+ fn: (argv) -> false
+ help:
+ help: """
+ naught help [cmd]
+
+ Displays help for cmd
+ """
+ fn: (argv) ->
+ if argv.length is 1 and (cmd = cmds[argv[0]])?
+ console.log(cmd.help)
+ else
+ printUsage()
+ true
+
+if (cmd = cmds[process.argv[2]])?
+ if not cmd.fn(process.argv.slice(3))
+ console.log(cmd.help)
+else
+ printUsage()
View
47 src/wrapper.co
@@ -0,0 +1,47 @@
+const
+ cluster = require('cluster')
+ assert = require('assert')
+
+argv = process.argv.slice(2)
+worker_count = parseInt(argv.shift())
+cwd = argv.shift()
+script = argv.shift()
+cluster.setupMaster do
+ exec: script
+ args: [cwd].concat(argv)
+
+workers = (cluster.fork() for i from 0 til worker_count)
+new_workers = []
+waiting_for = null
+
+!function onNewWorkerOnline
+ console.debug "#{new_workers.length} new workers online. Disconnecting 1 old worker."
+ old_worker = workers.shift()
+ old_worker.send \shutdown
+ waiting_for := \old
+ old_worker.once \exit, !->
+ console.debug "Old worker exited. #{workers.length} old workers online."
+ if workers.length is 0
+ assert new_workers.length is worker_count
+ waiting_for := null
+ [workers, new_workers] = [new_workers, workers]
+ console.debug "Ready for new deployment."
+ else
+ startNewWorker()
+
+!function startNewWorker
+ # replace workers one at a time
+ console.debug "Spawning 1 new worker."
+ new_workers.push new_worker = cluster.fork()
+ waiting_for := \new
+ new_worker.once \online, onNewWorkerOnline
+
+process.on \SIGUSR1, !->
+ if waiting_for?
+ console.error "Cannot deploy - already in progress."
+ else
+ startNewWorker()
+
+# cancel ongoing deployment
+process.on \SIGUSR2, !->
+ ...
View
11 test/simple/server.js
@@ -0,0 +1,11 @@
+var http;
+
+http = require("http");
+
+http.createServer(function(req, resp) {
+ if (req.path === "/die") {
+ throw new Error("unhandled exception");
+ } else {
+ resp.end("hi");
+ }
+}).listen(11904);
View
15 test/simple/test.js
@@ -0,0 +1,15 @@
+var exec;
+
+exec = require("child_process").exec;
+
+function doTest() {
+ exec("naught start server.js", {
+ cwd: __dirname
+ }, function(error, stdout, stderr) {
+
+ });
+ naught start server.js
+ assert server is running
+ hit localhost:11600/die
+ assert server is running
+}

0 comments on commit 43fd35d

Please sign in to comment.
Something went wrong with that request. Please try again.