Skip to content
Browse files

node/server: added experimental support for cluster.js

  • Loading branch information...
1 parent 118e105 commit 8bfe7d91eb181b68deb3ead22e5171396f51b52d @jeromeetienne committed Mar 1, 2011
Showing with 4,630 additions and 1 deletion.
  1. +15 −0 node/server/cluster.js
  2. BIN node/server/node_modules/cluster/._History.md
  3. BIN node/server/node_modules/cluster/._Readme.md
  4. BIN node/server/node_modules/cluster/._package.json
  5. BIN node/server/node_modules/cluster/._test.js
  6. +3 −0 node/server/node_modules/cluster/.gitignore
  7. +3 −0 node/server/node_modules/cluster/.gitmodules
  8. +6 −0 node/server/node_modules/cluster/.npmignore
  9. +112 −0 node/server/node_modules/cluster/History.md
  10. +22 −0 node/server/node_modules/cluster/LICENSE
  11. +8 −0 node/server/node_modules/cluster/Makefile
  12. +98 −0 node/server/node_modules/cluster/Readme.md
  13. BIN node/server/node_modules/cluster/docs/._api.md
  14. BIN node/server/node_modules/cluster/docs/._cli.md
  15. BIN node/server/node_modules/cluster/docs/._debug.md
  16. BIN node/server/node_modules/cluster/docs/._logger.md
  17. BIN node/server/node_modules/cluster/docs/._pidfiles.md
  18. BIN node/server/node_modules/cluster/docs/._reload.md
  19. BIN node/server/node_modules/cluster/docs/._repl.md
  20. BIN node/server/node_modules/cluster/docs/._stats.md
  21. +198 −0 node/server/node_modules/cluster/docs/api.md
  22. +59 −0 node/server/node_modules/cluster/docs/cli.md
  23. +27 −0 node/server/node_modules/cluster/docs/debug.md
  24. +33 −0 node/server/node_modules/cluster/docs/logger.md
  25. +41 −0 node/server/node_modules/cluster/docs/pidfiles.md
  26. +39 −0 node/server/node_modules/cluster/docs/reload.md
  27. +164 −0 node/server/node_modules/cluster/docs/repl.md
  28. +45 −0 node/server/node_modules/cluster/docs/stats.md
  29. +2 −0 node/server/node_modules/cluster/index.js
  30. BIN node/server/node_modules/cluster/lib/._cluster.js
  31. BIN node/server/node_modules/cluster/lib/._master.js
  32. BIN node/server/node_modules/cluster/lib/._worker.js
  33. +48 −0 node/server/node_modules/cluster/lib/cluster.js
  34. +755 −0 node/server/node_modules/cluster/lib/master.js
  35. +53 −0 node/server/node_modules/cluster/lib/mixins/receiver.js
  36. +214 −0 node/server/node_modules/cluster/lib/plugins/cli.js
  37. +108 −0 node/server/node_modules/cluster/lib/plugins/debug.js
  38. +146 −0 node/server/node_modules/cluster/lib/plugins/logger.js
  39. +64 −0 node/server/node_modules/cluster/lib/plugins/pidfiles.js
  40. +120 −0 node/server/node_modules/cluster/lib/plugins/reload.js
  41. +189 −0 node/server/node_modules/cluster/lib/plugins/repl.js
  42. +113 −0 node/server/node_modules/cluster/lib/plugins/stats.js
  43. +51 −0 node/server/node_modules/cluster/lib/utils.js
  44. +199 −0 node/server/node_modules/cluster/lib/worker.js
  45. BIN node/server/node_modules/cluster/node_modules/log/._History.md
  46. BIN node/server/node_modules/cluster/node_modules/log/._Makefile
  47. BIN node/server/node_modules/cluster/node_modules/log/._Readme.md
  48. BIN node/server/node_modules/cluster/node_modules/log/._index.js
  49. BIN node/server/node_modules/cluster/node_modules/log/._package.json
  50. +12 −0 node/server/node_modules/cluster/node_modules/log/History.md
  51. +14 −0 node/server/node_modules/cluster/node_modules/log/Makefile
  52. +95 −0 node/server/node_modules/cluster/node_modules/log/Readme.md
  53. BIN node/server/node_modules/cluster/node_modules/log/examples/._file.js
  54. BIN node/server/node_modules/cluster/node_modules/log/examples/._file.log
  55. BIN node/server/node_modules/cluster/node_modules/log/examples/._reader.js
  56. BIN node/server/node_modules/cluster/node_modules/log/examples/._stdout.js
  57. +18 −0 node/server/node_modules/cluster/node_modules/log/examples/file.js
  58. +8 −0 node/server/node_modules/cluster/node_modules/log/examples/file.log
  59. +15 −0 node/server/node_modules/cluster/node_modules/log/examples/reader.js
  60. +16 −0 node/server/node_modules/cluster/node_modules/log/examples/stdout.js
  61. +329 −0 node/server/node_modules/cluster/node_modules/log/index.html
  62. +2 −0 node/server/node_modules/cluster/node_modules/log/index.js
  63. +250 −0 node/server/node_modules/cluster/node_modules/log/lib/log.js
  64. +1 −0 node/server/node_modules/cluster/node_modules/log/package.json
  65. +1 −0 node/server/node_modules/cluster/package.json
  66. +3 −0 node/server/node_modules/cluster/support/should/.gitmodules
  67. +22 −0 node/server/node_modules/cluster/support/should/History.md
  68. +6 −0 node/server/node_modules/cluster/support/should/Makefile
  69. +248 −0 node/server/node_modules/cluster/support/should/Readme.md
  70. +2 −0 node/server/node_modules/cluster/support/should/index.js
  71. +91 −0 node/server/node_modules/cluster/support/should/lib/eql.js
  72. +548 −0 node/server/node_modules/cluster/support/should/lib/should.js
  73. +8 −0 node/server/node_modules/cluster/support/should/package.json
  74. +6 −1 node/server/server.js
View
15 node/server/cluster.js
@@ -0,0 +1,15 @@
+#!/usr/bin/env node
+/**
+ * cluster script - https://github.com/learnboost/cluster
+*/
+
+var cluster = require('cluster');
+
+cluster('./server.js')
+ .set('workers', 1) // only 1 workers
+ .use(cluster.logger('logs'))
+ .use(cluster.stats())
+ .use(cluster.pidfiles('pids'))
+ .use(cluster.cli())
+ .use(cluster.repl(8888))
+ .listen(8667);
View
BIN node/server/node_modules/cluster/._History.md
Binary file not shown.
View
BIN node/server/node_modules/cluster/._Readme.md
Binary file not shown.
View
BIN node/server/node_modules/cluster/._package.json
Binary file not shown.
View
BIN node/server/node_modules/cluster/._test.js
Binary file not shown.
View
3 node/server/node_modules/cluster/.gitignore
@@ -0,0 +1,3 @@
+testing
+test.js
+nohup.out
View
3 node/server/node_modules/cluster/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "support/should"]
+ path = support/should
+ url = git://github.com/visionmedia/should.js.git
View
6 node/server/node_modules/cluster/.npmignore
@@ -0,0 +1,6 @@
+test
+testing
+examples
+*.sock
+*.pid
+*.log
View
112 node/server/node_modules/cluster/History.md
@@ -0,0 +1,112 @@
+
+0.3.1 / 2011-02-28
+==================
+
+ * Added `cluster(filename)` support. Closes #45
+ This is highly recommended, view the API docs
+ on the site for more info.
+
+0.3.0 / 2011-02-28
+==================
+
+ * Added "worker exception" event. Closes #41
+ * Added `listen()` host dns resolution. Closes #35
+ * Added `pidfiles()` helper `master.pidof(name)`
+ * Added; `reload()` ignoring _node_modules_ and similar dirs. Closes #31
+ * Fixed master __PPID__ reference. Closes #38
+ * Fixed restart __SIGQUIT__ default
+ * Fixed; using `-g` for graceful shutdown instead of duplicate `-s`. Closes #39
+
+0.2.4 / 2011-02-25
+==================
+
+ * Added `Master#preventDefault` support to clean `cli()`.
+ Plugins can now tell master to "prevent its default behaviour", aka
+ listening for connections.
+
+ * Fixed bug preventing consistent envs. Closes #37 [reported by sambarnes]
+ This caused `require.paths` to be altered.
+
+ * Fixed; throw `pidfiles()` related errors, instead of ignoring
+
+0.2.3 / 2011-02-21
+==================
+
+ * Fixed `reload()` plugin; protect against cyclic restarts.
+
+0.2.2 / 2011-02-21
+==================
+
+ * Added __SIGCHLD__ trap to notify master of killed worker.
+ This means that master can now recover a child that
+ is __KILL__ed.
+ * Removed `Master#workerKilled()` call from worker
+
+0.2.1 / 2011-02-21
+==================
+
+ * Added `Master#do()`
+
+0.2.0 / 2011-02-21
+==================
+
+ * Added; maintaining worker count on __SIGCHLD__. Closes #28
+ * Added; defaulting `reload()` to the servers root dir
+ * Changed; `reload()` filtering out non-js files. Closes #30
+ * Removed __SIGHUP__ trap from worker
+
+0.1.1 / 2011-02-18
+==================
+
+ * Added vhost example
+ * Added restarts stat
+ * Added `'all'` env support, `in('all')` executing regardless
+ of the environment. Useful when `listen()`ing on the same port
+ regardless.
+
+ * Changed; `working directory` setting defaulting to the script directory (POLS)
+
+0.1.0 / 2011-02-18
+==================
+
+ * Added TCP echo server example
+ * Added REPL `shutdown()` function
+ * Added REPL `stop()` function
+ * Added master spawning strategy
+ On restart, master now spawns a new master to accept
+ connections while the previous works (and master) finish
+ and die off.
+ * Added `Master#in()` for environment based usage. Closes #22
+ For example:
+ cluster(server)
+ .in('development')
+ .use(cluster.debug())
+ .use(cluster.repl())
+ .listen(3000)
+ .in('production')
+ .use(cluster.logger())
+ .listen(80);
+
+ * Fixed some test race-conditions
+ * Fixed event leak. Closes #18
+
+0.0.4 / 2011-02-17
+==================
+
+ * Fixed `stats()` / `repl()` breakage when used with 0.2.x due to os mod. Closes #16
+ * Changed; close _REPL_ connections on shutdown
+
+0.0.3 / 2011-02-16
+==================
+
+ * Added log dependency to _package.json_. Closes #14
+
+0.0.2 / 2011-02-15
+==================
+
+ * Fixed `process.setuid()` typo
+
+0.0.1 / 2011-02-15
+==================
+
+ * Initial commit
View
22 node/server/node_modules/cluster/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2011 LearnBoost <dev@learnboost.com>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
8 node/server/node_modules/cluster/Makefile
@@ -0,0 +1,8 @@
+
+test:
+ @./test/run
+
+test-debug:
+ @./test/run debug
+
+.PHONY: test test-debug
View
98 node/server/node_modules/cluster/Readme.md
@@ -0,0 +1,98 @@
+
+# Cluster
+
+ [Cluster](http://learnboost.github.com/cluster) is an extensible multi-core server manager for [node.js](http://nodejs.org).
+
+## Installation
+
+ $ npm install cluster
+
+## Features
+
+ - zero-downtime restart
+ - hard shutdown support
+ - graceful shutdown support
+ - resuscitates workers
+ - maintains worker count, even if worker was _SIGKILL_ed.
+ - workers commit suicide when master dies
+ - spawns one worker per cpu (by default)
+ - extensible via plugins
+ - bundled plugins
+ - [cli](http://learnboost.github.com/cluster/docs/cli.html): provides a command-line interface for your cluster
+ - [debug](http://learnboost.github.com/cluster/docs/debug.html): verbose debugging information
+ - [logger](http://learnboost.github.com/cluster/docs/logger.html): master / worker logs
+ - [pidfiles](http://learnboost.github.com/cluster/docs/pidfiles.html): writes master / worker pidfiles
+ - [reload](http://learnboost.github.com/cluster/docs/reload.html): reloads workers when files change
+ - [repl](http://learnboost.github.com/cluster/docs/repl.html): perform real-time administration
+ - [stats](http://learnboost.github.com/cluster/docs/stats.html): adds real-time statistics to the `repl` plugin
+ - supports node 0.2.x
+ - supports node 0.4.x
+ - supports TCP servers
+
+## Example
+
+ var cluster = require('cluster')
+ , http = require('http');
+
+ var server = http.createServer(function(req, res){
+ console.log('%s %s', req.method, req.url);
+ var body = 'Hello World';
+ res.writeHead(200, { 'Content-Length': body.length });
+ res.end(body);
+ });
+
+ cluster(server)
+ .use(cluster.logger('logs'))
+ .use(cluster.stats())
+ .use(cluster.pidfiles('pids'))
+ .use(cluster.cli())
+ .use(cluster.repl(8888))
+ .listen(3000);
+
+# Screencasts
+
+ - Cluster [Introduction](http://screenr.com/X8v)
+
+## Running Tests
+
+First:
+
+ $ git submodule update --init
+
+Then:
+
+ $ make test
+
+Actively tested with node:
+
+ - 0.2.6
+ - 0.4.1
+
+## Authors
+
+ * TJ Holowaychuk
+
+## License
+
+(The MIT License)
+
+Copyright (c) 2011 LearnBoost &lt;dev@learnboost.com&gt;
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
BIN node/server/node_modules/cluster/docs/._api.md
Binary file not shown.
View
BIN node/server/node_modules/cluster/docs/._cli.md
Binary file not shown.
View
BIN node/server/node_modules/cluster/docs/._debug.md
Binary file not shown.
View
BIN node/server/node_modules/cluster/docs/._logger.md
Binary file not shown.
View
BIN node/server/node_modules/cluster/docs/._pidfiles.md
Binary file not shown.
View
BIN node/server/node_modules/cluster/docs/._reload.md
Binary file not shown.
View
BIN node/server/node_modules/cluster/docs/._repl.md
Binary file not shown.
View
BIN node/server/node_modules/cluster/docs/._stats.md
Binary file not shown.
View
198 node/server/node_modules/cluster/docs/api.md
@@ -0,0 +1,198 @@
+
+## API
+
+ The Cluster API at its core is extremely simple, all we need to do is pass
+ our tcp or http `server` to `cluster()`, then call `listen()` as we would on the `http.Server` itself.
+
+
+ var cluster = require('../')
+ , http = require('http');
+
+ var server = http.createServer(function(req, res){
+ res.writeHead(200);
+ res.end('Hello World');
+ });
+
+ cluster(server)
+ .listen(3000);
+
+ Alternatively (and recommended) is to export your server instance via `module.exports`, and supply a path to `cluster()`. For example _app.js_:
+
+ module.exports = http.createServer(....);
+
+ and _server.js_ with our cluster logic, allowing our server to be `require()`ed within tests, and preventing potential issues by having open database connections etc within the master processes, as only the workers need access to the `server` instance.
+
+ cluster('app')
+ .listen(3000);
+
+ A good example if this, is a long-lived database connection. Our _app.js_ may have this initialized at the top, which although will work fine stand-alone, may cause cluster's master processes to hang when restarting or closing due to the connection remaining active in the event loop.
+
+ var db = redis.createClient();
+
+### Plugins
+
+ A plugin simple a function that accepts the `master` process. Most plugin functions _return_ another anonymous function, allowing them to accept options, for example:
+
+ function myPlugin(path){
+ return function(master) {
+ // do stuff
+ }
+ }
+
+ To use them, all we need to do is pass it to the `use()` method:
+
+ cluster(server)
+ .use(myPlugin('/some/path'))
+ .listen(3000);
+
+ To use a plugin that is bundled with Cluster simply grab it from the `cluster` object:
+
+ cluster(server)
+ .use(cluster.logger())
+ .listen(3000);
+
+### Settings
+
+ Below are the settings available:
+
+ - `workers` Number of workers to spawn, defaults to the number of CPUs or `1`
+ - `working directory` Working directory defaulting to the script's dir
+ - `backlog` Connection backlog, defaulting to 128
+ - `socket path` Master socket path defaulting to `./`
+ - `timeout` Worker shutdown timeout in milliseconds, defaulting to `60000`
+ - `user` User id / name
+ - `group` Group id / name
+
+ We can take what we have now, and go on to apply settings using the `set(option, value)` method. For example:
+
+ cluster(server)
+ .set('working directory', '/')
+ .set('workers', 5)
+ .listen(3000);
+
+### Signals
+
+ Cluster performs the following actions when handling signals:
+
+ - `SIGINT` hard shutdown
+ - `SIGTERM` hard shutdown
+ - `SIGQUIT` graceful shutdown
+ - `SIGUSR2` restart workers
+
+### Events
+
+ The following events are emitted, useful for plugins or general purpose logging etc.
+
+ - `start`. When the server is starting (pre-spawn)
+ - `worker`. When a worker is spawned, passing the `worker`
+ - `listening`. When the server is listening for connections (post-spawn)
+ - `closing`. When master is gracefully shutting down
+ - `close`. When master has completed shutting down
+ - `worker killed`. When a worker has died
+ - `worker exception`. Worker uncaughtException. Receives the worker and exception object
+ - `kill`. When a `signal` is being sent to all workers
+ - `restarting`. Restart requested by REPL or signal. Receives an object
+ which can be patched in order to preserve plugin state.
+ - `restart`. Restart complete, new master established, previous killed.
+ Receives an object with state preserved by the `restarting` even,
+ patched in the previous master.
+
+### Master#state
+
+ Current state of the master process, one of:
+
+ - `active`
+ - `hard shutdown`
+ - `graceful shutdown`
+
+### Master#isWorker
+
+ `true` when the script is executed as a worker.
+
+ cluster = cluster(server).listen(3000);
+
+ if (cluster.isWorker) {
+ // do something
+ }
+
+### Master#isMaster
+
+`true` when the script is executed as master.
+
+ cluster = cluster(server).listen(3000);
+
+ if (cluster.isMaster) {
+ // do something
+ }
+
+### Master#set(option, value)
+
+ Set `option` to `value`.
+
+### Master#use(plugin)
+
+ Register a `plugin` for use.
+
+### Master#in(env)
+
+ Conditionally perform the following action, if
+ __NODE_ENV__ matches `env`.
+
+ cluster(server)
+ .in('development').use(cluster.debug())
+ .in('development').listen(3000)
+ .in('production').listen(80);
+
+ The environment conditionals may be applied to several calls:
+
+ cluster(server)
+ .set('working directory', '/')
+ .in('development')
+ .set('workers', 1)
+ .use(cluster.logger('logs', 'debug'))
+ .use(cluster.debug())
+ .listen(3000)
+ .in('production')
+ .set('workers', 4)
+ .use(cluster.logger())
+ .use(cluster.pidfiles())
+ .listen(80);
+
+ If we perform the same action for environments, set them before
+ the first `in()` call, or use `in('all')`.
+
+ cluster(server)
+ .set('working directory', '/')
+ .do(function(){
+ console.log('some arbitrary action');
+ })
+ .in('development')
+ .set('workers', 1)
+ .use(cluster.logger('logs', 'debug'))
+ .use(cluster.debug())
+ .in('production')
+ .set('workers', 4)
+ .use(cluster.logger())
+ .use(cluster.pidfiles())
+ .in('all')
+ .listen(80);
+
+### Master#spawn(n)
+
+ Spawn `n` additional workers.
+
+### Master#close()
+
+ Graceful shutdown, waits for all workers to reply before exiting.
+
+### Master#destroy()
+
+ Hard shutdown, immediately kill all workers.
+
+### Master#restart([signal])
+
+ Defaults to a graceful restart, spawning a new master process, and sending __SIGQUIT__ to the previous master process. Alternatively a custom `signal` may be passed.
+
+### Master#kill([signal])
+
+ Sends __SIGTERM__ or `signal` to all worker processes. This method is used by `Master#restart()`, `Master#close()` etc.
View
59 node/server/node_modules/cluster/docs/cli.md
@@ -0,0 +1,59 @@
+
+## CLI
+
+ Adds a command-line interface to your cluster.
+
+### Usage
+
+This plugin requires that you use `pidfiles()`
+above `cli()`, so that the pidfile directory
+is exposed.
+
+ cluster(server)
+ .use(cluster.pidfiles())
+ .use(cluster.cli())
+ .listen(3000);
+
+Once set up our server script serves as both
+the master, and the CLI. For example we may
+still launch the server(s) as shown below.
+
+ $ nohup node server.js &
+
+However now we may also utilize commands
+provided by this plugin.
+
+ $ node server.js status
+
+ master 3281 alive
+ worker 0 3282 dead
+ worker 1 3283 alive
+ worker 2 3284 alive
+ worker 3 3285 alive
+
+ $ node server.js restart
+ $ node server.js shutdown
+
+For more command information use `--help`.
+
+ $ node server.js --help
+
+### Defining CLI Commands
+
+ Plugins may define additional commands, simply by invoking `cluster.cli.define()` passing the flag(s), a callback function,
+ and a description. Below is the implementation of `--help` for reference:
+
+ var cli = require('cluster').cli;
+
+ cli.define('-h, --help, help', function(master){
+ console.log('\n Usage: node <file> <command>\n');
+ commands.forEach(function(command){
+ console.log(' '
+ + command.flags.join(', ')
+ + '\n '
+ + '\033[90m' + command.desc + '\033[0m'
+ + '\n');
+ });
+ console.log();
+ process.exit();
+ }, 'Show help information');
View
27 node/server/node_modules/cluster/docs/debug.md
@@ -0,0 +1,27 @@
+
+## Debug
+
+Outputs verbose debugging information to _stderr_.
+
+ info - master started
+ info - worker 0 spawned
+ info - worker 1 spawned
+ info - worker 2 spawned
+ info - worker 3 spawned
+ info - worker 2 connected
+ info - worker 0 connected
+ info - worker 3 connected
+ info - worker 1 connected
+ info - listening for connections
+ ^C info - shutting down
+ warning - kill(SIGKILL)
+ info - shutdown complete
+ warning - worker 2 died
+ warning - worker 0 died
+ warning - worker 3 died
+
+## Usage
+
+ cluster(server)
+ .use(cluster.debug())
+ .listen(3000);
View
33 node/server/node_modules/cluster/docs/logger.md
@@ -0,0 +1,33 @@
+
+## Logger
+
+ File-based logging of both the _master_ and _worker_ processes.
+
+### Usage
+
+The `logger([path[, level]])` plugin accepts an optional `path`, and optional `level` to control the verbosity of the master process logs. By default the log level is _info_.
+
+Outputting to `./logs`:
+
+ cluster(server)
+ .use(cluster.logger())
+ .listen(3000);
+
+
+Outputting to `./tmp/logs`:
+
+ cluster(server)
+ .use(cluster.logger('tmp/logs'))
+ .listen(3000);
+
+Outputting to `/var/log/node` with a log level of `debug`:
+
+ cluster(server)
+ .use(cluster.logger('/var/log/node', 'debug'))
+ .listen(3000);
+
+Generated files:
+
+ master.log
+ workers.access.log
+ workers.error.log
View
41 node/server/node_modules/cluster/docs/pidfiles.md
@@ -0,0 +1,41 @@
+
+## PID Files
+
+ Saves out PID files, for example:
+
+ master.pid
+ worker.0.pid
+ worker.1.pid
+ worker.2.pid
+ worker.3.pid
+
+### Usage
+
+The `pidfiles([path])` plugin saves pid (process-id) files to the given `path` or `./pids`.
+
+save to `./pids`:
+
+ cluster(server)
+ .use(cluster.pidfiles())
+ .listen(3000);
+
+save to `/var/run/node`:
+
+ cluster(server)
+ .use(cluster.logger('/var/run/node'))
+ .listen(3000);
+
+### master.pidfiles
+
+ The pidfiles directory.
+
+### master.pidof(name)
+
+ Return a __PID__ for the given `name`.
+
+ master.pidof('master')
+ // => 5978
+
+ master.pidof('worker.0')
+ // => 5979
+
View
39 node/server/node_modules/cluster/docs/reload.md
@@ -0,0 +1,39 @@
+
+## Reload
+
+ Restart the server the given js `files` have changed.
+ `files` may be several directories, filenames, etc, defaulting
+ to the script's directory.
+
+### Options
+
+ - `signal` Signal to send, defaults to __SIGTERM__
+ - `interval` Watcher interval, defaulting to `100`
+
+### Usage
+
+ The `reload(paths[, signal])` plugin accepts a single path, or an array of paths, watching for __mtime__ changes, and re-loading the workers when a change has been made. By default the __SIGTERM__ signal is sent, killing the workers immediately, however we may pass a `signal` for graceful termination as well.
+
+ Reload when files in `./` (`__dirname`) change:
+
+ cluster(server)
+ .use(cluster.reload())
+ .listen(3000);
+
+ Reload when files in `./lib` change:
+
+ cluster(server)
+ .use(cluster.reload('lib'))
+ .listen(3000);
+
+ Reload when files in `./lib`, `./tests`, or the `./index.js` file change:
+
+ cluster(server)
+ .use(cluster.reload(['lib', 'tests', 'index.js']))
+ .listen(3000);
+
+ Graceful shutdown:
+
+ cluster(server)
+ .use(cluster.reload('lib', { signal: 'SIGQUIT' }))
+ .listen(3000);
View
164 node/server/node_modules/cluster/docs/repl.md
@@ -0,0 +1,164 @@
+
+## REPL
+
+ Provides live administration tools for inspecting state, spawning and killing workers, and more. The __REPL__ plugin itself is extensible, for example the `stats()` plugin provides a __REPL__ function named `stats()`.
+
+### Usage
+
+ The `repl([port | path])` accepts a `port` or unix domain socket `path`, after which you may telnet to at any time.
+
+Launch the __REPL__ on port _8888_:
+
+ cluster(server)
+ .use(cluster.repl(8888, '127.0.0.1'))
+ .listen(3000);
+
+Start a telnet session:
+
+ $ telnet localhost 8888
+
+ cluster> help()
+
+ Commands
+ help(): Display help information
+ spawn(n): Spawn one or more additional workers
+ pids(): Output process ids
+ kill(id, signal): Send signal or SIGTERM to the given worker
+ shutdown(): Gracefully shutdown server
+ stop(): Hard shutdown
+ restart(): Gracefully restart all workers
+ echo(msg): echo the given message
+ stats(): Display server statistics
+
+
+### pids()
+
+ Outputs the master / worker process ids.
+
+ cluster> pids()
+
+ pids
+ master: 1799
+ worker #0: 1801
+ worker #1: 1802
+ worker #2: 1803
+ worker #3: 1804
+
+### spawn()
+
+ Spawn an additional worker.
+
+ cluster> spawn()
+ spawning 1 worker
+ cluster> pids()
+
+ pids
+ master: 1799
+ worker #0: 1801
+ worker #1: 1802
+ worker #2: 1803
+ worker #3: 1804
+ worker #4: 1809
+
+### spawn(n)
+
+ Spawn `n` workers:
+
+ cluster> spawn(4)
+ spawning 4 workers
+ cluster> pids()
+
+ pids
+ master: 1817
+ worker #0: 1818
+ worker #1: 1819
+ worker #2: 1820
+ worker #3: 1821
+ worker #4: 1825
+ worker #5: 1826
+ worker #6: 1827
+ worker #7: 1828
+
+### kill(id[, signal])
+
+Kill worker `id` with the given `signal` or __SIGTERM__. For graceful termination use __SIGQUIT__.
+
+ cluster> pids()
+
+ pids
+ master: 1835
+ worker #0: 1837
+ worker #1: 1838
+ worker #2: 1839
+ worker #3: 1840
+
+ cluster> kill(2)
+ sent SIGTERM to worker #2
+ cluster> kill(3)
+ sent SIGTERM to worker #3
+ cluster> pids()
+
+ pids
+ master: 1835
+ worker #0: 1837
+ worker #1: 1838
+ worker #2: 1843
+ worker #3: 1844
+
+### restart()
+
+ Gracefully restart all workers.
+
+ cluster> pids()
+
+ pids
+ master: 1835
+ worker #0: 1837
+ worker #1: 1838
+ worker #2: 1843
+ worker #3: 1844
+
+ cluster> restart()
+ restarting 4 workers
+ cluster> pids()
+
+ pids
+ master: 1835
+ worker #0: 1845
+ worker #1: 1849
+ worker #2: 1848
+ worker #3: 1847
+
+### Defining REPL Functions
+
+ To define a function accessible to the __REPL__, all we need to do is call `cluster.repl.define()`, passing the function, as well as a description string.
+
+ Below we define the `echo()` function, simply printing the input `msg` given. As you can see our function receivers the `Master` instance, the __REPL__ `sock`, and any arguments that were passed. For example `echo("test")` would pass the `msg` as `"test"`, and `echo("foo", "bar")` would pass `msg` as `"foo"`, and `arguments[3]` as `"bar"`.
+
+ repl.define('echo', function(master, sock, msg){
+ sock.write(msg + '\n');
+ }, 'echo the given message');
+
+ Shown below is a more complete example.
+
+ var cluster = require('../')
+ , repl = cluster.repl
+ , http = require('http');
+
+ var server = http.createServer(function(req, res){
+ var body = 'Hello World';
+ res.writeHead(200, { 'Content-Length': body.length });
+ res.end(body);
+ });
+
+ // custom repl function
+
+ repl.define('echo', function(master, sock, msg){
+ sock.write(msg + '\n');
+ }, 'echo the given message');
+
+ // $ telnet localhots 8888
+
+ cluster(server)
+ .use(repl(8888))
+ .listen(3000);
View
45 node/server/node_modules/cluster/docs/stats.md
@@ -0,0 +1,45 @@
+
+## Stats
+
+ The stats plugin collects statistics from the events emitter by the master process, and exposes a `stats()` __REPL__ function.
+
+
+### Usage
+
+To utilize simply `use()` both the `stats()` and `repl()` plugins.
+
+ cluster(server)
+ .use(cluster.stats())
+ .use(cluster.repl(8888))
+ .listen(3000);
+
+Telnet to the repl:
+
+ $ telnet localhost 8888
+
+### stats()
+
+ After manually killing two workers, the stats below show information regarding system load average, uptime, total workers spawned, deaths, worker-specific stats and more.
+
+ cluster> stats()
+
+ Master
+ os: Darwin 10.5.0
+ state: active
+ started: Fri, 11 Feb 2011 16:58:48 GMT
+ uptime: 2 minutes
+ workers: 4
+ deaths: 2
+
+ Resources
+ load average: 0.35 0.23 0.15
+ cores utilized: 4 / 4
+ memory at boot (free / total): 2.18gb / 4.00gb
+ memory now (free / total): 2.08gb / 4.00gb
+
+ Workers
+ uptime #0: 2 minutes
+ uptime #1: 2 minutes
+ uptime #2: 1 minute
+ uptime #3: 22 seconds
+
View
2 node/server/node_modules/cluster/index.js
@@ -0,0 +1,2 @@
+
+module.exports = require('./lib/cluster');
View
BIN node/server/node_modules/cluster/lib/._cluster.js
Binary file not shown.
View
BIN node/server/node_modules/cluster/lib/._master.js
Binary file not shown.
View
BIN node/server/node_modules/cluster/lib/._worker.js
Binary file not shown.
View
48 node/server/node_modules/cluster/lib/cluster.js
@@ -0,0 +1,48 @@
+
+/*!
+ * Cluster
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var Master = require('./master')
+ , fs = require('fs');
+
+/**
+ * Export `start` as the module.
+ */
+
+exports = module.exports = start;
+
+/**
+ * Library version.
+ */
+
+exports.version = '0.3.1';
+
+/**
+ * Start a new `Master` with the given `server`.
+ *
+ * @param {http.Server} server
+ * @return {Master}
+ * @api public
+ */
+
+function start(server) {
+ return new Master(server);
+}
+
+/**
+ * Expose middleware via lazy-requires.
+ */
+
+fs.readdirSync(__dirname + '/plugins').forEach(function(plugin){
+ plugin = plugin.replace('.js', '');
+ exports.__defineGetter__(plugin, function(){
+ return require('./plugins/' + plugin);
+ });
+});
View
755 node/server/node_modules/cluster/lib/master.js
@@ -0,0 +1,755 @@
+
+/*!
+ * Cluster - Master
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var Worker = require('./worker')
+ , EventEmitter = require('events').EventEmitter
+ , binding = process.binding('net')
+ , dirname = require('path').dirname
+ , spawn = require('child_process').spawn
+ , ENOENT = process.version >= 'v0.4.0'
+ ? require('constants').ENOENT
+ : process.ENOENT
+ , net = require('net')
+ , fs = require('fs')
+ , os;
+
+// COMPAT:
+
+try {
+ os = require('os');
+} catch (err) {
+ // ignore
+}
+
+// COMPAT:
+
+net.Socket = net.Stream;
+
+// COMPAT:
+
+if (!net.isIP) net.isIP = binding.isIP;
+
+/**
+ * Node binary.
+ */
+
+var node = process.execPath
+
+/**
+ * Start a new `Master` with the given `server` or filename to
+ * a node module exporting a server.
+ *
+ * Options:
+ *
+ * - `workers` Number of workers to spawn, defaults to the number of CPUs
+ * - 'working directory` Working directory defaulting to the script's dir
+ * - 'backlog` Connection backlog, defaulting to 128
+ * - 'socket path` Master socket path defaulting to `./`
+ * - 'timeout` Worker shutdown timeout in milliseconds, defaulting to 60,000
+ * - 'user` User id / name
+ * - 'group` Group id / name
+ *
+ * Events:
+ *
+ * - `start`. When the server is starting (pre-spawn)
+ * - `worker`. When a worker is spawned, passing the `worker`
+ * - `listening`. When the server is listening for connections (post-spawn)
+ * - `closing`. When master is gracefully shutting down
+ * - `close`. When master has completed shutting down
+ * - `worker killed`. When a worker has died
+ * - `worker exception`. Worker uncaughtException. Receives the worker / exception
+ * - `kill`. When a `signal` is being sent to all workers
+ * - `restarting`. Restart requested by REPL or signal. Receives an object
+ * which can be patched in order to preserve plugin state.
+ * - `restart`. Restart complete, new master established, previous died.
+ * Receives an object with state preserved by the `restarting` event.
+ *
+ * Signals:
+ *
+ * - `SIGINT` hard shutdown
+ * - `SIGTERM` hard shutdown
+ * - `SIGQUIT` graceful shutdown
+ * - `SIGUSR2` graceful restart
+ * - `SIGHUP` graceful restart
+ *
+ * @param {net.Server|String} server
+ * @return {Master}
+ * @api public
+ */
+
+var Master = module.exports = function Master(server) {
+ var self = this;
+ this.server = server;
+ this.plugins = [];
+ this.children = [];
+ this.state = 'active';
+
+ // grab server root
+ this.cmd = process.argv.slice(1);
+ this.dir = dirname(this.cmd[0]);
+
+ // defaults
+ this.options = {
+ 'backlog': 128
+ , 'working directory': this.dir
+ , 'socket path': this.dir
+ , 'timeout': 60000
+ };
+
+ // parent master pid
+ this.ppid = process.env.CLUSTER_PARENT_PID
+ ? parseInt(process.env.CLUSTER_PARENT_PID, 10)
+ : null;
+
+ // environment
+ this.env = process.env.NODE_ENV || 'development';
+
+ // process is a worker
+ this.isWorker = !! process.env.CLUSTER_MASTER_PID;
+
+ // process is a child (worker or master replacement)
+ this.isChild = this.isWorker || !! process.env.CLUSTER_REPLACEMENT_MASTER;
+
+ // process is master
+ this.isMaster = ! this.isWorker;
+
+ // process id
+ this.pid = process.pid;
+ if (this.isMaster) process.env.CLUSTER_MASTER_PID = this.pid;
+
+ // custom worker fds, defaults to std{out,err}
+ this.customFds = [1, 2];
+
+ // tcp server for IPC
+ this._server = net.createServer(function(sock){
+ sock.setEncoding('ascii');
+ sock.on('data', self.frame.bind(self));
+ });
+};
+
+/**
+ * Interit from `EventEmitter.prototype`.
+ */
+
+Master.prototype.__proto__ = EventEmitter.prototype;
+
+/**
+ * Worker is a receiver.
+ */
+
+require('./mixins/receiver')(Master.prototype);
+
+/**
+ * Resolve `path` relative to the server file being executed.
+ *
+ * @param {String} path
+ * @return {String}
+ * @api public
+ */
+
+Master.prototype.resolve = function(path){
+ return '/' == path[0]
+ ? path
+ : this.dir + '/' + path;
+};
+
+/**
+ * Return the path of the unix domain socket used for IPC.
+ *
+ * @return {String}
+ * @api public
+ */
+
+Master.prototype.__defineGetter__('socketPath', function(){
+ var pid = process.env.CLUSTER_MASTER_PID || this.pid;
+ return this.options['socket path'] + '/cluster.' + pid + '.sock';
+});
+
+/**
+ * Return `true` when the environment set by `Master#in()`
+ * matches __NODE_ENV__.
+ *
+ * @return {Boolean}
+ * @api private
+ */
+
+Master.prototype.__defineGetter__('environmentMatches', function(){
+ if (this._env)
+ return this.env == this._env || 'all' == this._env;
+ return true;
+});
+
+/**
+ * Invoke masters's `method` with worker `id`. (called from Worker)
+ *
+ * @param {Number} id
+ * @param {String} method
+ * @param {...} args
+ * @api private
+ */
+
+Master.prototype.call = function(id, method){
+ var args = Array.prototype.slice.call(arguments)
+ , id = args.shift()
+ , method = args.shift();
+ this.sock.write(JSON.stringify({
+ method: method
+ , args: args
+ , id: id
+ }), 'ascii');
+};
+
+/**
+ * Defer `http.Server#listen()` call.
+ *
+ * @param {Number|String} port or unix domain socket path
+ * @param {String|Function} host or callback
+ * @param {Function} callback
+ * @return {Master} for chaining
+ * @api public
+ */
+
+Master.prototype.listen = function(port, host, callback){
+ var self = this;
+ if (this.preventDefault) return;
+ if (this.environmentMatches) {
+ if (this.isWorker) {
+ var worker = new Worker(this);
+ this.sock = net.createConnection(this.socketPath);
+ this.sock.on('connect', function(){
+ worker.start();
+ });
+ } else {
+ this.port = port;
+ if ('function' == typeof host) callback = host, host = null;
+ this.host = host;
+ this.callback = callback;
+ if (this.isChild) {
+ this.acceptFd();
+ } else {
+ this.createSocket(function(err, fd){
+ if (err) throw err;
+ self.fd = fd;
+ self.start();
+ });
+ }
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Conditionally perform the following action, if
+ * __NODE_ENV__ matches `env`.
+ *
+ * Examples:
+ *
+ * cluster(server)
+ * .in('development').use(cluster.debug())
+ * .in('development').listen(3000)
+ * .in('production').listen(80);
+ *
+ * @param {String} env
+ * @return {Master} self or stubs
+ * @api public
+ */
+
+Master.prototype.in = function(env){
+ this._env = env;
+ return this;
+};
+
+/**
+ * Set option `key` to `val`.
+ *
+ * @param {String} key
+ * @param {Mixed} val
+ * @return {Master} for chaining
+ * @api public
+ */
+
+Master.prototype.set = function(key, val){
+ if (this.environmentMatches) this.options[key] = val;
+ return this;
+};
+
+/**
+ * Invoke `fn(master)`.
+ *
+ * @param {Function} fn
+ * @api public
+ */
+
+Master.prototype.do = function(fn){
+ if (this.environmentMatches) fn.call(this, this);
+ return this;
+};
+
+/**
+ * Check if `option` has been set.
+ *
+ * @param {String} option
+ * @return {Boolean}
+ * @api public
+ */
+
+Master.prototype.has = function(option){
+ return !! this.options[option];
+};
+
+/**
+ * Use the given `plugin`.
+ *
+ * @param {Function} plugin
+ * @return {Master} for chaining
+ * @api public
+ */
+
+Master.prototype.use = function(plugin){
+ if (this.environmentMatches) {
+ this.plugins.push(plugin);
+ if (!this.isWorker) plugin(this);
+ }
+ return this;
+};
+
+/**
+ * Create listening socket and callback `fn(err, fd)`.
+ *
+ * @return {Function} fn
+ * @api private
+ */
+
+Master.prototype.createSocket = function(fn){
+ var self = this
+ , ipv;
+
+ // explicit host
+ if (this.host) {
+ // ip
+ if (ipv = net.isIP(this.host)) {
+ fn(null, binding.socket('tcp' + ipv));
+ // lookup
+ } else {
+ require('dns').lookup(this.host, function(err, ip, ipv){
+ if (err) return fn(err);
+ self.host = ip;
+ fn(null, binding.socket('tcp' + ipv));
+ });
+ }
+ // local socket
+ } else if ('string' == typeof this.port) {
+ fn(null, binding.socket('unix'));
+ // only port
+ } else if (this.port) {
+ fn(null, binding.socket('tcp4'));
+ }
+};
+
+/**
+ * Start master process.
+ *
+ * - defaults workers to the number of CPUs available
+ * - creates listening socket
+ *
+ * @api private
+ */
+
+Master.prototype.start = function(){
+ var self = this;
+
+ // signal handlers
+ process.on('SIGINT', this.destroy.bind(this));
+ process.on('SIGTERM', this.destroy.bind(this));
+ process.on('SIGQUIT', this.close.bind(this));
+ process.on('SIGUSR2', this.restart.bind(this));
+ process.on('SIGHUP', this.restart.bind(this));
+ process.on('SIGCHLD', this.maintainWorkerCount.bind(this));
+
+ // Default worker to the # of cpus
+ if (!this.has('workers')) {
+ this.set('workers', os
+ ? os.cpus().length
+ : 1);
+ }
+
+ // TCP server for IPC
+ this._server.listen(this.socketPath, function(){
+ self.emit('start');
+
+ // change working dir
+ process.chdir(self.options['working directory']);
+
+ // user
+ var user = self.options.user;
+ if (user) process.setuid(user);
+
+ // group
+ var group = self.options.group;
+ if (group) process.setgid(group);
+
+ // spawn workers
+ self.spawn(self.options.workers);
+
+ // set # of pending workers
+ self.starting = true;
+ self.pendingWorkers = self.options.workers;
+ });
+};
+
+/**
+ * Maintain worker count, re-spawning if necessary.
+ *
+ * @api private
+ */
+
+Master.prototype.maintainWorkerCount = function(){
+ this.children.forEach(function(worker){
+ var pid = worker.proc.pid;
+ if (!pid) this.workerKilled(worker);
+ }, this);
+};
+
+/**
+ * Spawn `n` workers.
+ *
+ * @param {Number} n
+ * @api private
+ */
+
+Master.prototype.spawn = function(n){
+ while (n--) this.spawnWorker();
+};
+
+/**
+ * Spawn a worker with optional `id`.
+ *
+ * @param {Number} id
+ * @return {Worker}
+ * @api private
+ */
+
+Master.prototype.spawnWorker = function(id){
+ var worker = new Worker(this).spawn();
+
+ // id given
+ if ('number' == typeof id) {
+ this.children[id] = worker;
+ worker.id = id;
+ // generate an id
+ } else {
+ var len = this.children.push(worker);
+ worker.id = len - 1;
+ }
+
+ // TODO: refactor
+ var json = JSON.stringify({
+ method: 'connect'
+ , args: [worker.id, this.options.timeout]
+ });
+ worker.sock.write(json, 'ascii', this.fd);
+
+ // emit
+ this.emit('worker', worker);
+
+ return worker;
+};
+
+/**
+ * Graceful shutdown, wait for all workers
+ * to reply before exiting.
+ *
+ * @api public
+ */
+
+Master.prototype.close = function(){
+ this.state = 'graceful shutdown';
+ this.emit('closing');
+ this.kill('SIGQUIT');
+ this.pendingDeaths = this.children.length;
+};
+
+/**
+ * Hard shutdwn, immediately kill all workers.
+ *
+ * @api public
+ */
+
+Master.prototype.destroy = function(){
+ this.state = 'hard shutdown';
+ this.emit('closing');
+ this.kill('SIGKILL');
+ this._destroy();
+};
+
+
+/**
+ * Restart all workers, by sending __SIGQUIT__
+ * or `sig` to them, enabling master to re-spawn.
+ *
+ * @param {String} sig
+ * @return {ChildProcess} replacement master process
+ * @api public
+ */
+
+Master.prototype.restart = function(sig){
+ var data = {}
+ , proc = this.spawnMaster();
+
+ // pass object to plugins, allowing them
+ // to patch it, and utilize the data in
+ // the new Master
+ this.emit('restarting', data);
+ proc.sock.write(JSON.stringify({
+ method: 'connectMaster'
+ , args: [sig || 'SIGQUIT']
+ }), 'ascii', this.fd);
+
+ // TODO: refactor
+ this.on('close', function(){
+ proc.sock.write(JSON.stringify({
+ method: 'masterKilled'
+ , args: [data]
+ }), 'ascii');
+ });
+
+ return proc;
+};
+
+/**
+ * Spawn a new master process.
+ *
+ * @return {ChildProcess}
+ * @api private
+ */
+
+Master.prototype.spawnMaster = function(){
+ var fds = binding.socketpair()
+ , customFds = [fds[0], 1, 2]
+ , env = {};
+
+ // merge current env
+ for (var key in process.env) {
+ env[key] = process.env[key];
+ }
+
+ delete env.CLUSTER_MASTER_PID;
+ env.CLUSTER_REPLACEMENT_MASTER = 1;
+ env.CLUSTER_PARENT_PID = this.pid;
+
+ // spawn new master process
+ var proc = spawn(node, this.cmd, {
+ customFds: customFds
+ , env: env
+ });
+
+ proc.stdin = new net.Socket(fds[0]);
+
+ // unix domain socket for ICP + fd passing
+ proc.sock = new net.Socket(fds[1], 'unix');;
+ return proc;
+};
+
+/**
+ * Master replacement connected.
+ *
+ * @param {String} sig
+ * @api private
+ */
+
+Master.prototype.connectMaster = function(sig){
+ var self = this;
+ this.on('listening', function(){
+ process.kill(self.ppid, sig);
+ });
+};
+
+/**
+ * Original master has died aka 'retired',
+ * we now fire the 'restart' event.
+ *
+ * @param {Object} data
+ * @api private
+ */
+
+Master.prototype.masterKilled = function(data){
+ this.emit('restart', data);
+};
+
+/**
+ * Accept fd from parent master, then `start()`.
+ *
+ * @api private
+ */
+
+Master.prototype.acceptFd = function(){
+ var self = this
+ , stdin = new net.Socket(0, 'unix');
+
+ // set fd and start master
+ stdin.setEncoding('ascii');
+ stdin.on('fd', function(fd){
+ self.fd = fd;
+ self.start();
+ });
+
+ // frame commands from the parent master
+ stdin.on('data', this.frame.bind(this));
+ stdin.resume();
+};
+
+/**
+ * Immediate shutdown, used by `Master#close()`
+ * and `Master#destroy()` when all workers have shutdown.
+ *
+ * @api private
+ */
+
+Master.prototype._destroy = function(){
+ var self = this;
+ self._server.on('close', function(){
+ self.emit('close');
+ process.nextTick(process.exit.bind(process));
+ }).close();
+};
+
+/**
+ * Worker is connected, on boot
+ * wait for all workers to connect,
+ * then emit the 'listening' event.
+ *
+ * @param {Worker} worker
+ * @api private
+ */
+
+Master.prototype.connect = function(worker){
+ var self = this;
+ this.emit('worker connected', worker);
+ if (this.starting && !--this.pendingWorkers) {
+ this.startListening(!this.isChild);
+ }
+};
+
+/**
+ * Start listening, when `bind` is `true` the socket
+ * will be bound, and will start listening for connections.
+ *
+ * @param {Boolean} bind
+ * @api private
+ */
+
+Master.prototype.startListening = function(bind){
+ var self = this;
+
+ // remove unix domain socket
+ if ('string' == typeof this.port && bind) {
+ fs.unlink(this.port, function(err){
+ if (ENOENT != err.errno) throw err;
+ listen();
+ });
+ } else {
+ listen();
+ }
+
+ // bind / listen
+ function listen() {
+ if (bind) {
+ binding.bind(self.fd, self.port, self.host);
+ binding.listen(self.fd, self.options.backlog);
+ }
+ self.callback && self.callback();
+ self.emit('listening');
+ }
+};
+
+/**
+ * The given `worker` has been killed.
+ * Emit the "worker killed" event, remove
+ * the worker, and re-spawn depending on
+ * the master state.
+ *
+ * @api private
+ */
+
+Master.prototype.workerKilled = function(worker){
+ this.emit('worker killed', worker);
+
+ // always remove worker
+ this.removeWorker(worker.id);
+
+ // state specifics
+ switch (this.state) {
+ case 'hard shutdown':
+ break;
+ case 'graceful shutdown':
+ --this.pendingDeaths || this._destroy();
+ break;
+ default:
+ this.spawnWorker(worker.id);
+ }
+};
+
+/**
+ * `worker` received exception `err`.
+ *
+ * @api private
+ */
+
+Master.prototype.workerException = function(worker, err){
+ this.emit('worker exception', worker, err);
+};
+
+/**
+ * Received worker timeout.
+ *
+ * @api private
+ */
+
+Master.prototype.workerTimeout = function(worker, timeout){
+ this.emit('worker timeout', worker, timeout);
+};
+
+/**
+ * Worker waiting on `connections` to close.
+ *
+ * @api private
+ */
+
+Master.prototype.workerWaiting = function(worker, connections){
+ this.emit('worker waiting', worker, connections);
+};
+
+/**
+ * Remove worker `id`.
+ *
+ * @param {Number} id
+ * @api public
+ */
+
+Master.prototype.removeWorker = function(id){
+ delete this.children[id];
+};
+
+/**
+ * Send `sig` to all worker processes, defaults to __SIGTERM__.
+ *
+ * @param {String} sig
+ * @api public
+ */
+
+Master.prototype.kill = function(sig){
+ var self = this;
+ this.emit('kill', sig);
+ this.children.forEach(function(worker){
+ worker.proc.kill(sig);
+ });
+};
View
53 node/server/node_modules/cluster/lib/mixins/receiver.js
@@ -0,0 +1,53 @@
+
+/*!
+ * Cluster - receiver mixin
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+module.exports = function(obj){
+
+ /**
+ * Frame incoming command, buffering the given `chunk`
+ * until a frame is complete.
+ *
+ * @param {Socket} sock
+ * @param {String} chunk
+ * @api private
+ */
+
+ obj.frame = function(chunk){
+ this._buf = this._buf || '';
+ this.braces = this.braces || 0;
+
+ for (var i = 0, len = chunk.length; i < len; ++i) {
+ if ('{' == chunk[i]) ++this.braces;
+ if ('}' == chunk[i]) --this.braces;
+ this._buf += chunk[i];
+ if (0 == this.braces) {
+ var worker
+ , obj = JSON.parse(this._buf);
+ this._buf = '';
+ if ('number' == typeof obj.id) worker = this.children[obj.id];
+ this.invoke(obj.method, obj.args, worker);
+ }
+ }
+ };
+
+ /**
+ * Invoke `method` with the given `args`.
+ *
+ * @param {String} method
+ * @param {Mixed} args
+ * @param {Worker} worker
+ * @api private
+ */
+
+ obj.invoke = function(method, args, worker){
+ if (!method) return;
+ if (!Array.isArray(args)) args = [args];
+ if (worker) args.unshift(worker);
+ if (!this[method]) throw new Error('method ' + method + '() does not exist');
+ this[method].apply(this, args);
+ };
+};
View
214 node/server/node_modules/cluster/lib/plugins/cli.js
@@ -0,0 +1,214 @@
+
+/*!
+ * Cluster - cli
+ * Copyright (c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var fs = require('fs')
+ , Log = require('log')
+ , ESRCH = process.version >= 'v0.4.0'
+ ? require('constants').ESRCH
+ : process.ESRCH
+
+/**
+ * Commands.
+ */
+
+var commands = [];
+
+/**
+ * Adds a command-line interface to your cluster.
+ *
+ * This plugin requires that you use `pidfiles()`
+ * above `cli()`, so that the pidfile directory
+ * is exposed.
+ *
+ * Examples:
+ *
+ * cluster(server)
+ * .use(cluster.pidfiles())
+ * .use(cluster.cli())
+ * .listen(3000);
+ *
+ * Once set up our server script serves as both
+ * the master, and the CLI. For example we may
+ * still launch the server(s) as shown below.
+ *
+ * $ nohup node server.js &
+ *
+ * However now we may also utilize commands
+ * provided by this plugin.
+ *
+ * $ node server.js status
+ *
+ * master 3281 dead
+ * worker 0 3282 dead
+ *
+ * For more command information use `--help`.
+ *
+ * $ node server.js --help
+ *
+ * @return {Function}
+ * @api public
+ */
+
+exports = module.exports = function(){
+ return function(master){
+ requirePIDs(master);
+
+ var args = process.argv.slice(2)
+ , len = commands.length
+ , command
+ , arg;
+
+ // parse arguments
+ while (args.length) {
+ arg = args.shift();
+ for (var i = 0; i < len; ++i) {
+ command = commands[i];
+ if (~command.flags.indexOf(arg)) {
+ master.preventDefault = true;
+ command.callback(master);
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Define command `name` with the given callback `fn(master)`
+ * and a short `desc`.
+ *
+ * @param {String} name
+ * @param {Function} fn
+ * @param {String} desc
+ * @return {Object} exports for chaining
+ * @api public
+ */
+
+var define = exports.define = function(name, fn, desc){
+ commands.push({
+ flags: name.split(/ *, */)
+ , desc: desc
+ , callback: fn
+ });
+ return exports;
+};
+
+/**
+ * Report master / worker status based on
+ * the PID files saved by the pidfiles()
+ * plugin.
+ */
+
+define('-s, --status, status', function(master){
+ var dir = master.pidfiles
+ , files = fs.readdirSync(dir);
+
+ // null signal failed previous
+ // to this release
+ if (process.version < 'v0.4.1') {
+ console.log('status will not work with node < 0.4.1');
+ console.log('due to SIGTERM globbering the null signal');
+ process.exit(1);
+ }
+
+ console.log();
+
+ // only pids
+ files.filter(function(file){
+ return file.match(/\.pid$/);
+ // check status
+ }).forEach(function(file){
+ var name = file.replace('.pid', '')
+ , pid = master.pidof(name)
+ , name = name.replace('.', ' ')
+ , color
+ , status;
+
+ try {
+ process.kill(pid, 0);
+ status = 'alive';
+ color = '36';
+ } catch (err) {
+ if (ESRCH == err.errno) {
+ color = '31';
+ status = 'dead';
+ } else {
+ throw err;
+ }
+ }
+
+ console.log(' %s\033[90m %d\033[0m \033[' + color + 'm%s\033[0m'
+ , name
+ , pid
+ , status);
+ });
+
+ console.log();
+}, 'Output cluster status');
+
+/**
+ * Restart workers.
+ */
+
+define('-r, --restart, restart', function(master){
+ process.kill(master.pidof('master'), 'SIGUSR2');
+}, 'Restart master by sending the SIGUSR2 signal');
+
+/**
+ * Graceful shutdown.
+ */
+
+define('-g, --shutdown, shutdown', function(master){
+ process.kill(master.pidof('master'), 'SIGQUIT');
+}, 'Graceful shutdown by sending the SIGQUIT signal');
+
+/**
+ * Hard shutdown.
+ */
+
+define('-S, --stop, stop', function(master){
+ process.kill(master.pidof('master'), 'SIGTERM');
+}, 'Hard shutdown by sending the SIGTERM signal');
+
+/**
+ * Display help information.
+ */
+
+define('-h, --help, help', function(master){
+ console.log('\n Usage: node <file> <command>\n');
+ commands.forEach(function(command){
+ console.log(' '
+ + command.flags.join(', ')
+ + '\n '
+ + '\033[90m' + command.desc + '\033[0m'
+ + '\n');
+ });
+ console.log();
+}, 'Show help information');
+
+/**
+ * Output cluster version.
+ */
+
+define('-v, --version', function(master){
+ console.log(require('../cluster').version);
+}, 'Output cluster version');
+
+/**
+ * Require `pidfiles()` plugin.
+ *
+ * @param {Master} master
+ * @api private
+ */
+
+function requirePIDs(master) {
+ if (master.pidfiles) return;
+ throw new Error('cli() plugin requires pidfiles(), please add pidfiles() before cli()');
+}
View
108 node/server/node_modules/cluster/lib/plugins/debug.js
@@ -0,0 +1,108 @@
+
+/*!
+ * Cluster - debug
+ * Copyright (c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Logger.
+ */
+
+var log = {
+ debug: function(str){
+ console.error(' \033[90mdebug - %s\033[0m', str);
+ },
+ info: function(str){
+ console.error(' info \033[90m- %s\033[0m', str);
+ },
+ warning: function(str){
+ console.error(' \033[33mwarning\033[0m \033[90m- %s\033[0m', str);
+ },
+ error: function(str){
+ console.error(' \033[31merror\033[0m \033[90m- %s\033[0m', str);
+ }
+};
+
+/**
+ * Enable verbose debugging output.
+ *
+ * @return {Function}
+ * @api public
+ */
+
+module.exports = function(){
+ return function(master){
+
+ // start
+ master.on('start', function(){
+ log.info('master started');
+ });
+
+ // closing
+ master.on('closing', function(){
+ log.info('shutting down');
+ });
+
+ // close
+ master.on('close', function(){
+ log.info('shutdown complete');
+ });
+
+ // killing workers
+ master.on('kill', function(sig){
+ log.warning('kill(' + (sig || 'SIGTERM') + ')');
+ });
+
+ // worker died
+ master.on('worker killed', function(worker){
+ if ('restarting' == master.state) return;
+ log.warning('worker ' + worker.id + ' died');
+ });
+
+ // worker exception
+ master.on('worker exception', function(worker, err){
+ log.error('worker ' + worker.id + ' uncaught exception ' + err.message);
+ });
+
+ // worker is waiting on connections to be closed
+ master.on('worker waiting', function(worker, connections){
+ log.warning('worker ' + worker.id + ' waiting on ' + connections + ' connections');
+ });
+
+ // worker has timed out
+ master.on('worker timeout', function(worker, timeout){
+ log.warning('worker ' + worker.id + ' timed out after ' + timeout + 'ms');
+ });
+
+ // connection
+ master.on('worker connected', function(worker){
+ log.info('worker ' + worker.id + ' connected');
+ });
+
+ // worker
+ master.on('worker', function(worker){
+ log.info('worker ' + worker.id + ' spawned');
+ });
+
+ // listening
+ master.on('listening', function(){
+ log.info('listening for connections');
+ });
+
+ // restart requested
+ master.on('restarting', function(){
+ log.info('restart requested');
+ });
+
+ // restart complete
+ master.on('restart', function(){
+ log.info('restart complete');
+ });
+
+ // exit
+ process.on('exit', function(){
+ log.debug('exit');
+ });
+ }
+};
View
146 node/server/node_modules/cluster/lib/plugins/logger.js
@@ -0,0 +1,146 @@
+
+/*!
+ * Cluster - logger
+ * Copyright (c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var fs = require('fs')
+ , Log = require('log');
+
+/**
+ * Enable stdout / stderr logs for both the master
+ * process, as well as workers.
+ *
+ * These output to the given `dir`, or `./logs`
+ * relative to the server's file.
+ *
+ * Examples:
+ *
+ * // log to ./logs
+ * engine(server)
+ * .use(engine.logger())
+ * .listen(3000);
+ *
+ * // log to ./app/logs
+ * engine(server)
+ * .use(engine.logger('./app/logs'))
+ * .listen(3000);
+ *
+ * // log to /var/log/node
+ * engine(server)
+ * .use(engine.logger('/var/log/node'))
+ * .listen(3000);
+ *
+ * @param {String} dir
+ * @param {Number} level
+ * @return {Function}
+ * @api public
+ */
+
+module.exports = function(dir, level){
+ return function(master){
+ dir = master.resolve(dir || 'logs');
+
+ // master log
+ var stream = fs.createWriteStream(dir + '/master.log', { flags: 'a' });
+ var log = master.log = new Log(level || Log.INFO, stream);
+
+ // master events
+ master.on('start', function(){
+ log.info('master started');
+ });
+
+ // master is shutting down
+ master.on('closing', function(){
+ log.warning('shutting down master');
+ });
+
+ // master has closed and performed cleanup
+ master.on('close', function(){
+ log.info('shutdown complete');
+ });
+
+ // sending signal to all workers
+ master.on('kill', function(sig){
+ log.warning('sent kill(' + sig + ') to all workers');
+ });
+
+ // worker was killed
+ master.on('worker killed', function(worker){
+ if ('restarting' == master.state) return;
+ log.error('worker ' + worker.id + ' died');
+ });
+
+ // worker exception
+ master.on('worker exception', function(worker, err){
+ log.error('worker ' + worker.id + ' uncaught exception ' + err.message);
+ });
+
+ // worker is waiting on connections to be closed
+ master.on('worker waiting', function(worker, connections){
+ log.info('worker ' + worker.id + ' waiting on ' + connections + ' connections');
+ });
+
+ // worker has timed out
+ master.on('worker timeout', function(worker, timeout){
+ log.warning('worker ' + worker.id + ' timed out after ' + timeout + 'ms');
+ });
+
+ // worker connected to master
+ master.on('worker connected', function(worker){
+ log.debug('worker ' + worker.id + ' connected');
+ });
+
+ // restart requested