DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Snippets has posted 5883 posts at DZone. View Full User Profile

Ruby Daemon Module

07.10.2006
| 36410 views |
  • submit to reddit
        The following code lets you implement a daemon very easily, and also lets you write cleanup code.

Update: I made some small changes to make it look slightly better.

require 'fileutils'

module Daemon
  WorkingDirectory = File.expand_path(File.dirname(__FILE__))  

  class Base
    def self.pid_fn
      File.join(WorkingDirectory, "#{name}.pid")
    end
    
    def self.daemonize
      Controller.daemonize(self)
    end
  end
  
  module PidFile
    def self.store(daemon, pid)
      File.open(daemon.pid_fn, 'w') {|f| f << pid}
    end
    
    def self.recall(daemon)
      IO.read(daemon.pid_fn).to_i rescue nil
    end
  end
  
  module Controller
    def self.daemonize(daemon)
      case !ARGV.empty? && ARGV[0]
      when 'start'
        start(daemon)
      when 'stop'
        stop(daemon)
      when 'restart'
        stop(daemon)
        start(daemon)
      else
        puts "Invalid command. Please specify start, stop or restart."
        exit
      end
    end
    
    def self.start(daemon)
      fork do
        Process.setsid
        exit if fork
        PidFile.store(daemon, Process.pid)
        Dir.chdir WorkingDirectory
        File.umask 0000
        STDIN.reopen "/dev/null"
        STDOUT.reopen "/dev/null", "a"
        STDERR.reopen STDOUT
        trap("TERM") {daemon.stop; exit}
        daemon.start
      end
    end
  
    def self.stop(daemon)
      if !File.file?(daemon.pid_fn)
        puts "Pid file not found. Is the daemon started?"
        exit
      end
      pid = PidFile.recall(daemon)
      FileUtils.rm(daemon.pid_fn)
      pid && Process.kill("TERM", pid)
    end
  end
end

To use it, you can do something like the following:

class Counter < Daemon::Base
  def self.start
    @a = 0
    loop do
      @a += 1
    end
  end

  def self.stop
    File.open('result', 'w') {|f| f.puts "a = #{@a}"}
  end
end

Counter.daemonize
    

Comments

Snippets Manager replied on Sun, 2006/08/06 - 1:46am

I'm a newbie to Ruby, so forgive a dumb question: will a Ruby daemon fail to work properly if its working directory is set to "/", per daemon tradition? Would it play havoc with any on-the-fly code loading that would go on (which seems plausible)?

Snippets Manager replied on Mon, 2012/05/07 - 2:25pm

Good timing. I need a portion of this for something I'm working on right now :)