Daemonic

Daemonic is a tool that let's you turn your long running processes into daemons.

At a glance:

  • Designed to be external to and independent of your app.
  • Possibility to spawn multiple instances of your app.
  • Smart restart behavior, comparable to Unicorn.
  • Automatically restarts crashed instances of your app.

What can Daemonic do for me?

Say you have written your own daemon. It processes messages of a queue for example. You can start it by running ruby work.rb. Now the only thing you need to do is turn it into a proper daemon. This means daemonizing your process, managing restarts, adding concurrency, managing a pid file, etc.

This is a lot of work, quite error prone and frankly, very tedious. That's where Daemonic comes in. All you need to do, to start your message processor is:

$ daemonic --command "ruby work.rb" --pid work.pid --workers 5 --daemonize

And voila: you now have 5 instances of your worker, each in it's own process. This works just like Unicorn, but with any kind of process, not just Rack apps. You can restart all workers by sending the USR2 signal, just like in Unicorn too.

$ kill -USR2 `cat work.pid`

This will restart each process individually, meaning you never lose capacity.

There are some caveats though. For one, daemonic checks if the process is running, but it is very naive. It doesn't really know if the app is ready, just if it is running or not.

Secondly, if your app needs a shared resource, spawning multiple workers will not work. This is the case with web servers that need to bind to a port.

Also, your own worker needs to respond to the TERM and HUP signals. Daemonic expects the app to shut down after sending the TERM signal. It's up to you to finish up what you are doing.

Installation

You don't need to add daemonic to your Gemfile. You can simply install it and use it straight away.

gem install daemonic

Usage

Daemonic accepts command line options and can also read from a configuration file. The configuration file contains the same options as you would pass to the command line. Here is an example:

--command "ruby work.rb"
--workers 5
--pid tmp/worker.pid
--name my-worker
--log log/development.log
--daemonize

Then you can run Daemonic with the config option:

$ daemonic --config my-worker.conf

There are a bunch more options. See daemonic --help for the full list.

Signals

These are the signals daemonic responds to:

  • TERM/INT shuts down all workers and then the master
  • HUP will be forwarded to each worker and the master will reload the config file
  • USR2 will restart each worker, but not the master
  • TTIN increase the number of workers by one
  • TTOU decrease the number of workers by one

Creating a worker

Here's an example of a basic daemonic worker:

exiting = false

trap("TERM") { exiting = true }
trap("INT") { exiting = true }
trap("HUP") {
  # reload settings
}

puts "Booting worker number #{ENV["DAEMON_WORKER_NUMBER"]}"

SomePollingService.poll do
  # do your work here
  break if exiting
end

puts "Shutting down worker number #{ENV["DAEMON_WORKER_NUMBER"]}"

Most importantly:

  • Trap INT and TERM signals to cleanly stop your process

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

TODO

  • Ability to create init script like behavior.