Daemonize your rake task

2 Jan 2014

THIS IS NOT GOOD ANYMORE. The gem daemons doesn't seem to be well-maintained, and Ruby 2.0 has a cleaner way of daemonizing.


So, I have been stuck with daemonizing a rake task for a while. I would like to daemonize a rake task (or run a rake task in background) because I need to run a long-lived process in parallel with Rails.

Another requirement is that I would like to have only one process at all time. Therefore, when Rails is deployed (through Capistrano), the rake task should be killed and started with the newly-deployed code.

We can, of course, use the normal way that uses a bunch of magical bash script; it doesn't sound beautiful to me though…

I've found this gem daemons. It seems to work well with some weird issues:

  • The log dir needs to be an absolute path. Otherwise, it couldn't properly create a log file.
  • There is no source code. There is only gem.

I just host it on my own here: https://github.com/tanin47/daemons/tree/v1.1.9

Now let's jump to the code. Here is the rake task:

namespace :daemon do task :something => :environment do daemonizer = Daemonizer.new("test", "pids") daemonizer.stop # Allow only one instance to run at any moment daemonizer.start do puts "do something" end end end

I've decided to build my daemonizer, which is just a thin wrapper:

class Daemonizer < Struct.new(:app_name, :dir) def start(&block) Daemons.call({ :app_name => self.app_name, :dir_mode => :normal, :dir => self.dir, :log_dir => File.join(Dir.pwd, 'log'), # Require an absolute path :log_output => true, :backtrace => false }, &wrap(&block)) end def stop monitor = Daemons::Monitor.find(self.dir, self.app_name) return if monitor.nil? monitor.stop end private def wrap(&block) Proc.new do $stdout.sync = true $stderr.sync = true block.call end end end

With the code above, you can modify your capistrano to just run:

bundle exec rake daemon:something

The command will not be blocked.

Underneath all this, it will create a PID file under the folder pids. This file will ensure that there is only one instance running. (It is managed through Daemons::Monitor)


Graceful exiting

I've also implemented the graceful exiting mechanism. It will be helpful for most cases.

The rake task now looks like this:

namespace :daemon do task :something => :environment do is_running = true daemonizer = Daemonizer.new("test", "pids", File.join(Dir.pwd, 'log')) daemonizer.exit do is_running = false end daemonizer.stop # Allow only one instance to run at any moment daemonizer.start do while is_running puts "do something" sleep(1) end puts "Exited gracefully" end end end

And the daemonizer looks like this:

class Daemonizer < Struct.new(:app_name, :dir, :log_dir) def exit(&exit_block) @exit_block = exit_block end def start(block) Daemons.call({ :app_name => self.app_name, :dir_mode => :normal, :dir => self.dir, :log_dir => self.log_dir, # Require an absolute path :log_output => true, :backtrace => false }, &wrap(block)) end def stop monitor = Daemons::Monitor.find(self.dir, self.app_name) return if monitor.nil? monitor.stop end private def wrap(block) Proc.new do $stdout.sync = true $stderr.sync = true if @exit_block trap("TERM") { exit_on_double_signals } end block.call end end private def exit_on_double_signals @kill_count ||= 0 if @kill_count == 0 @kill_count += 1 @exit_block.call else puts "TERM is signaled twice. Therefore, we kill the process #{Process.pid} immediately." exit(0) end end end

Now you can conveniently set the exit block and exits your process gracefully.

I hope you like it!

Give it a kudos