Skip to content

Commit

Permalink
add support for cluster
Browse files Browse the repository at this point in the history
  • Loading branch information
shimaore committed Mar 21, 2017
1 parent 201e811 commit 4acbd3b
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 20 deletions.
1 change: 1 addition & 0 deletions docs/reference.md
Expand Up @@ -114,6 +114,7 @@ You can also pass the parameters in the `options` object; they are described in
* `host`: the hostname or IP address for the web server.
* `path`: IPC path; if present, ZappaJS will start an [IPC server](https://nodejs.org/api/net.html#net_server_listen_path_backlog_callback) instead of a TCP/IP server.
* `ready`: this function is called once the server is ready to accept requests.
* `server`: if the `server` option is set to the string `cluster`, ZappaJS will use `throng` to start and manage a Node.js cluster. In this case the function will not return anything useful; use the `ready` option to handle server startup.

The default port and host may also be specified as part of the environment variables, using `ZAPPA_PORT` and `ZAPPA_HOST`, respectively.

Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -22,6 +22,7 @@
"socket.io": "^1.7.3",
"socket.io-client": "^1.7.3",
"teacup": "^2.0.0",
"throng": "^4.0.0",
"uglify-js": "^2.8.4"
},
"devDependencies": {
Expand Down Expand Up @@ -73,7 +74,7 @@
],
"scripts": {
"pretest": "npm install -d",
"test": "coffee tests/index.coffee && mocha --compilers coffee.md:coffee-script/register mocha/tests/",
"test": "coffee tests/index.coffee && mocha --compilers coffee.md:coffee-script/register mocha/tests/ && coffee tests/cluster.coffee.md",
"prepublish": "coffee -o lib -c -M src/*.coffee.md",
"docs": "docco src/*.coffee.md",
"clean": "rm lib/*.js lib/*.js.map benchmarks/out/*.dat benchmarks/out/*.out tests/*.js",
Expand Down
124 changes: 105 additions & 19 deletions src/zappa.coffee.md
Expand Up @@ -44,6 +44,14 @@ Flatten array recursively (copied from Express's utils.js)
ret.push o
ret
Address-hash

connection_hash = ({remoteAddress},len) ->
sum = 0
for i in [0...remoteAddress.len]
sum += remoteAddress.charCodeAt 1
i % len
Zappa Application
=================

Expand Down Expand Up @@ -703,30 +711,108 @@ Takes a function and runs it as a zappa app. Optionally accepts a port number, a
when 'path' then ipc_path = v
else options[k] = v
zapp = zappa.app(root_function,options)
{server,app} = zapp
Listen for connections
----------------------

server.once 'listening', ->
addr = server.address()
channel = if typeof addr is 'string' then addr else addr.address + ':' + addr.port
debug """
Express server listening on #{channel} in #{app.settings.env} mode.
Zappa #{zappa.version} orchestrating the show.
listen = (zapp) ->
"""
{server} = zapp
if options.ready?
server.once 'listening', -> options.ready zapp
server.once 'listening', ->
addr = server.address()
channel = if typeof addr is 'string' then addr else addr.address + ':' + addr.port
debug """
Express server listening on #{channel},
Zappa #{zappa.version} orchestrating the show.
switch
when ipc_path
server.listen ipc_path
when host
server.listen port, host
else
server.listen port
"""
if options.ready?
options.ready zapp
return
switch
when ipc_path
server.listen ipc_path
when host
server.listen port, host
else
server.listen port
Cluster mode
------------

Inspired by https://github.com/elad/node-cluster-socket.io

if options.server is 'cluster'
throng = require 'throng'
cluster = require 'cluster'
net = require 'net'

Cluster master
--------------

master = ->
workers = []
cluster.on 'fork', (worker) ->
debug 'Adding worker', worker.id
workers.push worker
null
cluster.on 'exit', (worker) ->
index = workers.indexOf worker
debug 'Removing worker', worker.id, index
if index >= 0
workers.splice index, 1
null
server_options = pauseOnConnect: true
if options.https?
for own k,v of options.https
server_options[k] ?= v
server = net.createServer server_options, (connection) ->
worker = workers[ connection_hash connection, workers.length ]
worker.send 'sticky-session:connection', connection
zapp = {server}
listen zapp
start = (id) ->
debug "Starting worker #{id}"
http_module = switch
when options.http_module?
options.http_module
when options.https?
require 'https'
else
require 'http'
server = options.server = http_module.createServer()
{app} = zappa.app root_function, options
server.on 'request', app
process.on 'message', (message, connection) ->
return unless message is 'sticky-session:connection'
server.emit 'connection', connection
connection.resume()
throng {master,start}
zapp = null
Non-cluster (legacy) mode
-------------------------

else
zapp = zappa.app root_function, options
listen zapp
The value returned by `Zappa.run` (aka `Zappa`) is the global context.
The value returned by `Zappa.run` (aka `Zappa`) is the global context in legacy mode, `null` in cluster mode.

zapp
Expand Down
30 changes: 30 additions & 0 deletions tests/cluster.coffee.md
@@ -0,0 +1,30 @@
zappa = require '../src/zappa'
port = 15804
seem = require 'seem'
request = require 'superagent'
assert = require 'assert'
sleep = (timeout) ->
new Promise (resolve) ->
setTimeout resolve, timeout
do ->
ready = seem ->
yield sleep 1000
{text} = yield request.get "http://localhost:#{port}/"
assert text is 'cluster'
{text} = yield request.get "http://127.0.0.1:#{port}/"
assert text is 'cluster'
console.log 'Cluster test OK'
process.exit(0)
zappa {ready, server:'cluster', port}, ->
@get '/': 'cluster'

0 comments on commit 4acbd3b

Please sign in to comment.