├── .gitignore
├── README.md
├── bin
└── koa-cluster
├── lib
└── http.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled source #
2 | ###################
3 | *.com
4 | *.class
5 | *.dll
6 | *.exe
7 | *.o
8 | *.so
9 |
10 | # Packages #
11 | ############
12 | # it's better to unpack these files and commit the raw source
13 | # git has its own built in compression methods
14 | *.7z
15 | *.dmg
16 | *.gz
17 | *.iso
18 | *.jar
19 | *.rar
20 | *.tar
21 | *.zip
22 |
23 | # Logs and databases #
24 | ######################
25 | *.log
26 | *.sql
27 | *.sqlite
28 |
29 | # OS generated files #
30 | ######################
31 | .DS_Store*
32 | # Icon?
33 | ehthumbs.db
34 | Thumbs.db
35 |
36 | # Node.js #
37 | ###########
38 | lib-cov
39 | *.seed
40 | *.log
41 | *.csv
42 | *.dat
43 | *.out
44 | *.pid
45 | *.gz
46 |
47 | pids
48 | logs
49 | results
50 |
51 | node_modules
52 | npm-debug.log
53 |
54 | # Components #
55 | ##############
56 |
57 | /build
58 | /components
59 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Koa Cluster
3 |
4 | 
5 |
6 | Lightweight, opinionated clustering for Koa apps.
7 | Use this if you don't want the complexity of other process managers.
8 |
9 | ## Usage
10 |
11 | ```bash
12 | koa-cluster app.js
13 | ```
14 |
15 | ## Koa App
16 |
17 | You must export your Koa app like:
18 |
19 | ```js
20 | var app = module.exports = koa()
21 | ```
22 |
23 | Make sure you don't create a `http` server yourself.
24 | Don't call `app.listen()`.
25 | This will handle it automatically.
26 |
27 | ### Check Continue
28 |
29 | This automatically utilizes the `checkContinue` event.
30 | You should handle this wherever appropriate in your Koa app:
31 |
32 | ```js
33 | app.use(function* () {
34 | if (this.req.checkContinue) this.res.writeContinue();
35 | var body = yield parse(this);
36 | })
37 | ```
38 |
39 | ### Settings
40 |
41 | Custom settings are checked on the `app` that aren't part of Koa.
42 | You can set them however you'd like.
43 | These settings are:
44 |
45 | - `name` - your app's name
46 | - `port`
47 | - `hostname`
48 | - `maxHeadersCount`
49 | - `timeout`
50 |
51 | ## License
52 |
53 | The MIT License (MIT)
54 |
55 | Copyright (c) 2014 Jonathan Ong me@jongleberry.com
56 |
57 | Permission is hereby granted, free of charge, to any person obtaining a copy
58 | of this software and associated documentation files (the "Software"), to deal
59 | in the Software without restriction, including without limitation the rights
60 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
61 | copies of the Software, and to permit persons to whom the Software is
62 | furnished to do so, subject to the following conditions:
63 |
64 | The above copyright notice and this permission notice shall be included in
65 | all copies or substantial portions of the Software.
66 |
67 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
68 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
69 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
70 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
71 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
72 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
73 | THE SOFTWARE.
74 |
--------------------------------------------------------------------------------
/bin/koa-cluster:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var program = require('commander')
4 | var cluster = require('cluster')
5 |
6 | program
7 | .usage('')
8 | .option('-t, --title ', 'title of the process')
9 | .option('-p, --processes ', 'number of processes to use', parseInt)
10 | .option('-s, --startsecs ', 'number of seconds which children needs to'
11 | + ' stay running to be considered a successfull start [1]', parseInt, 1)
12 | .parse(process.argv)
13 |
14 | // make sure the file exists
15 | var filename = require('path').resolve(program.args[0])
16 | var requirename = require.resolve(filename)
17 | try {
18 | require('fs').statSync(requirename)
19 | } catch (err) {
20 | console.error('resolved %s to %s then %s', program.args[0], filename, requirename)
21 | console.error('however, %s does not exist', requirename)
22 | process.exit(1)
23 | }
24 |
25 | if (program.title) process.title = program.title
26 |
27 | cluster.setupMaster({
28 | exec: require.resolve('../lib/http.js'),
29 | execArgv: ['--harmony'],
30 | args: [requirename],
31 | })
32 |
33 | var procs;
34 |
35 | if (program.processes) {
36 | procs = program.processes;
37 | } else {
38 | var cpus = require('os').cpus().length
39 | procs = Math.ceil(0.75 * cpus)
40 | }
41 |
42 | for (var i = 0; i < procs; i++) cluster.fork()
43 |
44 | // remember the time which children have started in order
45 | // to stop everything on instant failure
46 | var startTime = process.hrtime()
47 |
48 | // http://nodejs.org/api/cluster.html#cluster_event_disconnect
49 | // not sure if i need to listen to the `exit` event
50 | cluster.on('disconnect', onDisconnect)
51 |
52 | // don't try to terminate multiple times
53 | var terminating = false
54 | process.on('SIGTERM', terminate)
55 | process.on('SIGINT', terminate)
56 |
57 | // i'm not even sure if we need to pass SIGTERM to the workers...
58 | function terminate() {
59 | if (terminating) return
60 | terminating = true
61 |
62 | // don't restart workers
63 | cluster.removeListener('disconnect', onDisconnect)
64 | // kill all workers
65 | Object.keys(cluster.workers).forEach(function (id) {
66 | console.log('sending kill signal to worker %s', id)
67 | cluster.workers[id].kill('SIGTERM')
68 | })
69 | }
70 |
71 | function onDisconnect(worker) {
72 | var timeSinceStart = process.hrtime(startTime)
73 | timeSinceStart = timeSinceStart[0] + timeSinceStart[1] / 1e9
74 | if (timeSinceStart < program.startsecs) {
75 | console.log('worker ' + worker.process.pid + ' has died'
76 | + ' before startsecs is over. stopping all.')
77 | process.exitCode = worker.process.exitCode || 1
78 | terminate()
79 | } else {
80 | console.log('worker ' + worker.process.pid + ' has died. forking.')
81 | cluster.fork()
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/lib/http.js:
--------------------------------------------------------------------------------
1 |
2 | var start = Date.now()
3 |
4 | var http = require('http')
5 | var cluster = require('cluster')
6 |
7 | var args = process.argv
8 |
9 | var app = require(args[args.length - 1])
10 | var callback = app.callback()
11 |
12 | var server = http.createServer()
13 | server.on('request', callback)
14 | server.on('checkContinue', function (req, res) {
15 | req.checkContinue = true
16 | callback(req, res)
17 | })
18 |
19 | // custom koa settings
20 | // defaults to http://nodejs.org/api/http.html#http_server_maxheaderscount
21 | server.maxHeadersCount = app.maxHeadersCount || 1000
22 | server.timeout = app.timeout || 120000
23 |
24 | server.listen(app.port, app.hostname, function (err) {
25 | if (err) throw err
26 | console.log('%s listening on port %s, started in %sms',
27 | app.name || 'koa app',
28 | this.address().port,
29 | Date.now() - start)
30 | })
31 |
32 | // don't try to close the server multiple times
33 | var closing = false
34 | process.on('SIGTERM', function () {
35 | close(0)
36 | })
37 | process.on('SIGINT', function () {
38 | close(0)
39 | })
40 | process.on('uncaughtException', function (err) {
41 | console.error(err.stack)
42 | close(1)
43 | })
44 | process.on('exit', function () {
45 | console.log('exiting worker %s', cluster.worker.id)
46 | })
47 |
48 | function close(code) {
49 | if (closing) return
50 | console.log('closing worker %s', cluster.worker.id)
51 | closing = true
52 |
53 | // to do: make this an option
54 | var killtimer = setTimeout(function () {
55 | process.exit(code)
56 | }, 30000)
57 |
58 | // http://nodejs.org/api/timers.html#timers_unref
59 | killtimer.unref()
60 | server.close()
61 | cluster.worker.disconnect()
62 | }
63 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "koa-cluster",
3 | "description": "Koa clustering and domain-based error handling utility",
4 | "version": "1.1.0",
5 | "author": {
6 | "name": "Jonathan Ong",
7 | "email": "me@jongleberry.com",
8 | "url": "http://jongleberry.com",
9 | "twitter": "https://twitter.com/jongleberry"
10 | },
11 | "repository": "koajs/cluster",
12 | "license": "MIT",
13 | "dependencies": {
14 | "commander": "2"
15 | },
16 | "bin": "bin/koa-cluster",
17 | "engines": {
18 | "node": ">= 4.0.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------