style (Supervised TCPServer, Yielding Listeners Easily)

style is a Ruby program that provides a supervised TCPServer (or TCPServers) with multiple listening childing processes. It allows the child processes to be killed and respawned while making sure that there are always child processes available to listen to requests. It automatically respawns dead child processes. It allows for increasing and decreasing the number of child processes on the fly.

style is distributed as a gem, and can be installed with:

sudo gem install ruby-style

Feedback/Bugs/Support Requests should be handled through RubyForge at rubyforge.org/projects/ruby-style/.

The RDoc is available at ruby-style.rubyforge.org.

Source is available at github: github.com/jeremyevans/ruby-style

Highlights

  • Always has child listeners available, so no connections are lost when listeners are restarted

  • Automatically restarts dead child processes

  • Supports both single port and multiple port clustering, with an arbitrary number of listeners per port

  • Supports increasing and decreasing the number of child processes per port on the fly

  • Supports both command line and yaml config file configuration

  • Is easily extensible to support running other frameworks and protocols other than the included ones (Rails; SCGI, Mongrel, Thin, and Evented Mongrel)

Running and configuration

To see the available commands and possible and default configuration options, just run the program without any arguments:

style [option value, …] (decrement|halt|increment|restart|run|start|stop)

Options:
 -a, --adapter       Adapter/Framework to use [rails]
 -b, --bind          IP address to bind to [127.0.0.1]
 -c, --config        Location of config file [style.yaml]
 -d, --directory     Working directory [.]
 -D, --debug         Run the program in the foreground without forking [No]
 -f, --fork          Number of listners on each port [1]
 -h, --handler       Handler/Server to use [mongrel]
 -k, --killtime      Number of seconds to wait when killing each child [2]
 -l, --logfile       Where to redirect STDOUT and STDERR [style.log]
 -n, --number        Number of ports to which to bind [1]
 -p, --port          Starting port to which to bind [9999]
 -P, --pidfile       Location of pid file [style.pid]
 -u, --unsupervised  Whether to run unsupervised [No]

Here’s what the various commands do:

  • decrement (USR2) - Decrease the number of listeners per port by 1

  • halt (TERM) - Immediately shutdown the child processes and exit

  • increment (USR1) - Increase the number of listeners per port by 1

  • restart (HUP) - Replace the current listeners with new listeners

  • run - Runs the supervisor and listening processes without detaching

  • start - Starts the supervisor process and the listening processes

  • stop (INT) - Gracefully shutdown the child processes and exit

All commands except start and run just send the listed signal to the preexisting supervisor process specified in the pid file.

The signals can be sent directly to style when started with the run command, which is the natural way to use style with runit or daemontools.

Note that the -d (–directory) option changes the working directory of the process, so the -c, -l, and -P options are relative to that.

Here’s a longer explanation of the options:

-a, --adapter       Adapter/Framework to use [rails]

This is the adapter/framework to use.  Support for Rails is
included in the distribution, and is special-cased. Any other value runs
require with the argument given.

-b, --bind          IP address to bind to [127.0.0.1]

This is the TCP/IP networking socket to bind to.  It defaults to the
loopback address because generally the web application runs on the same
physical server as the web server.  If this is not the case, change it to an
externally available IP, and make sure to lock down access to the port via a
firewall.

-c, --config        Location of config file [config/style.yaml]

This is the configuration file for style.  It is recommended that you use
this instead of the command line configuration, as it saves typing.  This
path is relative to the working directory, so if it is not inside the working
directory, make sure you specify an absolute path.  This option is not
configurable from the configuration file.

-d, --directory     Working directory [.]

This is the working directory of the process.  It should generally be the
path to the root of the application.  Alternatively, you can change to the
root of the application before hand and then not use this option. This option
is not configurable from the configuration file.

-D, --debug         Run the program in the foreground without forking [No]

This runs the program in the program without forking, aiding in debugging a
problematic configuration.

-f, --fork          Number of listners on each port [1]

This enables multiple child processes listening on each port.  It is
recommended that you use -f instead of -n for multiple listeners, since it
simplifies the configuration of the webserver, and can also eliminate
the need for a proxy such as pound or pen to handle this for you.  It
defaults to one process per port.  Note that when restarting processes, 
replacement processes are started before the currently listening processes
are killed, so it is possibly to have multiple processes listening on a port
even if this is left at one.

-h, --handler       Handler/Server to use [mongrel]

This is the handler/server to use.  Support for Mongrel, Evented Mongrel,
and SCGI is included in the distribution.  Support for other servers is
easy to add as long as the servers can be modified to use the socket
created by style ($STYLE_SOCKET).

-k, --killtime      Number of seconds to wait when killing each child [2]

This sets the time that style between sending shutdown signals to child
process, as well as the time between starting a replacement process and
killing an existing process.  When restarting, the amount of time spent
waiting should be between 2-3 * (killtime * number * fork).

-l, --logfile       Where to redirect STDOUT and STDERR  [log/style.log]

This is the location of the log file, relative to the working directory.
style itself doesn't output anything after it has detached from the listening
terminal, but child listening processes might output to STDOUT or STDERR.

-n, --number        Number of ports to which to bind [1]

This makes style start up multiple sockets, one per port starting with the
given port (so port, port+1, port+2, ..., port+n).  This makes webserver
configuration a little more difficult than with just using -f, and might
also require a separate proxy such as pound or pen, so you should try just
using -f first.

-p, --port          Starting port to which to bind [9999]

This is the starting (or only) port that style will use.  If -n is used, all
ports will be greater than this one.

-P, --pidfile       Location of pid file [log/style.pid]

This is the pid file, relative to the working directory.  The pid file is
necessary, as it is what is used by all commands other than start.  If
incorrect information is in the pid file, the processes won't be stopped when
they should be, and you will probably won't be able to start new processes
(because the ports will still be in use).

-u, --unsupervised  Whether to run unsupervised [No]

This starts child processes without using a supervisor process.  It is not
as reliable or as featureful as the regular supervised mode, but those may
not be necessary for smaller sites.  In unsupervised mode, only restart,
start, and stop are valid commands, child processes aren't automatically
restarted when they die, and you may lose connections during restarts.

Every one of the long options can also be specified in the config file. Also, the config file must be used if you want to specify any settings specific to adapter being used (such as modifying the Rails environment setting). See below for information on the adapter_config config file variable.

The config file

Example Rails+Mongrel style.yaml that listens on ports 8912 and 8193 on any interface, with three listeners per port. Note how the :adapter_config entry is used to set up settings specific to the Rails adapter, such as placing Rails in development mode. Ignore the pipes at the beginning of the line, blame RDoc limitations.

| ---
| :port: 8912
| :bind: ""
| :fork: 3
| :number: 2
| :killtime: 1
| :adapter: rails
| :handler: mongrel
| :adapter_config:
|   :environment: development
|   :log_file: log/rails-mongrel.log
|   :docroot: public
|   :num_processors: 99
|   :timeout: 100

Example Rails+SCGI style.yaml that has four listeners on port 8912:

| ---
| :port: 8912
| :fork: 4
| :number: 1 
| :adapter: rails
| :handler: scgi
| :adapter_config:
|   :logfile: log/railsscgi.log
|   :maxconns: 10
|   :environment: production

Configuration for running Rails with Thin on ports 3456-3459 with one listener on each port:

| ---
| :port: 3456
| :number: 4
| :adapter: rails
| :handler: thin

Very simple configuration that runs Ramaze (with the default start.rb runner) +Evented Mongrel in unsupervised mode on port 3000.

| ---
| :port: 3000
| :unsupervised: 1
| :adapter: start
| :handler: evented_mongrel

Using the generic adapter to host a camping application (you are responsible for having the camping application in a my_camping_app.rb file in your $LOAD_PATH that uses Mongrel to run camping):

| ---
| :port: 3301
| :adapter: my_camping_app
| :environment:
|    RACK_ENV: production

One important thing to note from the example above is that you can specify an :environment key in the config file that should be a hash. If it is provided, the hash under it will be added to the environment before the child processes are loaded.

How restarting works

Restarting works by starting a new child process, waiting for killtime, and then killing the exisiting process. It repeats this for all existing child processes.

For example:

# style idling with three listeners
Thu Sep  6 12:01:47 PDT 2007
11460 ??  I       0:00.00 ruby: RailsMongrelStyle dir:/home/billg/testrails supervisor (ruby)
 6540 ??  I       0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
  272 ??  I       0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
27808 ??  I       0:02.51 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
# Restart occurs, new process started (1207)
Thu Sep  6 12:01:49 PDT 2007
11460 ??  I       0:00.00 ruby: RailsMongrelStyle dir:/home/billg/testrails supervisor (ruby)
27808 ??  I       0:02.51 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
  272 ??  I       0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
 6540 ??  I       0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
 1207 ??  R       0:00.95 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
# Old process killed (27808)
Thu Sep  6 12:01:51 PDT 2007
11460 ??  I       0:00.01 ruby: RailsMongrelStyle dir:/home/billg/testrails supervisor (ruby)
 6540 ??  I       0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
  272 ??  I       0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
 1207 ??  I       0:02.51 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
# New process started (3204)
Thu Sep  6 12:01:53 PDT 2007
11460 ??  I       0:00.01 ruby: RailsMongrelStyle dir:/home/billg/testrails supervisor (ruby)
  272 ??  I       0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
 6540 ??  I       0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
 1207 ??  I       0:02.51 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
 3204 ??  R       0:01.01 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
# Old process killed (6540)
Thu Sep  6 12:01:55 PDT 2007
11460 ??  I       0:00.01 ruby: RailsMongrelStyle dir:/home/billg/testrails supervisor (ruby)
  272 ??  I       0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
 1207 ??  I       0:02.51 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
 3204 ??  I       0:02.49 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
# New process started (6766)
Thu Sep  6 12:01:57 PDT 2007
11460 ??  I       0:00.01 ruby: RailsMongrelStyle dir:/home/billg/testrails supervisor (ruby)
  272 ??  I       0:02.52 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
 1207 ??  I       0:02.51 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
 3204 ??  I       0:02.49 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
 6766 ??  R       0:00.99 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
# Old process killed (272)
Thu Sep  6 12:01:59 PDT 2007
11460 ??  I       0:00.01 ruby: RailsMongrelStyle dir:/home/billg/testrails supervisor (ruby)
 1207 ??  I       0:02.51 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
 3204 ??  I       0:02.49 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)
 6766 ??  I       0:02.49 ruby: RailsMongrelStyle dir:/home/billg/testrails port:8912 environment:production (ruby)

Result, three brand new listening processes, total time to restart is about 12 seconds (2 * (2 killtime * 1 number * 3 fork)).

Other handlers

To add support for another handler, make sure the instead of creating a socket, it uses the socket provided by style, available in the global variable $STYLE_SOCKET. See existing handler code to see how Mongrel, SCGI, and Event Machine were modified to support this.

Style loads handlers from style/handler relative to the load path, so you can add your own handlers without modifying the program, as long as you place the files somewhere in the load path.

Changes to the default config, logfile, and pidfile in 1.2.0

Previously, style had the following defaults:

  • config - config/style.yaml

  • logfile - log/style.log

  • pidfile - log/style.pid

These made sense because style was originally designed to serve Rails applications, which all have config and log directories. Now that style serves more types of applications, and few of those have config or log directories, these no longer made good defaults. The new defaults use the same filename, without the directory:

  • config - style.yaml

  • logfile - style.log

  • pidfile - style.pid

Changes to adapter handling in 1.2.0

Adapter handling was simplified in 1.2.0. Because there is no easy script that loads rails, there is still a rails adapter. However, for all other frameworks you would want to use, just specify the file you want to require as the adapter. This simplifies things and allows you to specify your adapter file on the command line:

style -a sinatra_runner start

Before, this wasn’t possible, you had to set up a configuration file or put the adapter file you wanted to use in a style/adapter subdirectory in the load path.

Upgrading from 1.1.*, previously using ramaze adapter

Previously, if you used the following configuration:

| ---
| :adapter: ramaze

You should change it to:

| ---
| :adapter: start

If you specified your own :runner via :adapter_config, just use that runner name for the :adapter instead of start.

The Ramaze adapter had some default settings that you might want to consider:

:test_connections=>false, :force=>true

You should make sure your ramaze runner sets the correct :adapter for Ramaze to use (it should use the same one as style uses). Having it set the :host and :port are good ideas, otherwise it may display those incorrectly. You should make sure your ramaze runner actually calls Ramaze.start, as well.

Upgrading from 1.1.*, previously using generic adapter

If you were previously using the generic adapter, the upgrade is simple. Change the following cofiguration from:

| ---
| :adapter: generic
| :adapter_config:
|   :script: cse

To:

| ---
| :adapter: cse

FAQ

Q: Does this run on Windows?

A: Only using debug mode (–debug, -D). All other modes use fork, which isn’t

supported on Windows.

Q: Does it work with Capistrano yet?

A: I haven’t tried. It probably can, but it will take a little custom work.