Daemonize like fetchmail

Has anyone used the Daemons library (http://daemons.rubyforge.org/) extensively? I'm trying to do something that doesn't appear covered by the examples, and I'm wondering if I need to reinvent the wheel.

What I'd like to do is to easily add functionality to my script similar to the way that fetchmail works, ie:

When starting the script with one command-line option (eg: --daemon), I daemonize it and start it running (saving the PID in a file).

When starting the script with another command-line option (eg: --stop), it finds the PID file and kills the instance.

In a sense, I'd like to combine the wrapper method given in the examples and the script it runs, but the wrapper example seems to absorb command-line options, and I want to call those functions from within Ruby instead.

Suggestions?

-Payton

ruby queue and dirwatch both do this.

   http://codeforpeople.com/lib/ruby/dirwatch/
   http://codeforpeople.com/lib/ruby/rq/

atomic creation of files is trickier than one might imagine. is this on *nix?
is so checkout my lockfile class and posixlock. it's very important to note
that there is ONE atomic creation operator on *nix and it is NOT open or
mkdir - symlink is the one. 'man lockfile' might be illuminating.

cheers.

-a

···

On Fri, 6 Jan 2006, Payton Swick wrote:

Has anyone used the Daemons library (http://daemons.rubyforge.org/\) extensively? I'm trying to do something that doesn't appear covered by the examples, and I'm wondering if I need to reinvent the wheel.

What I'd like to do is to easily add functionality to my script similar to the way that fetchmail works, ie:

When starting the script with one command-line option (eg: --daemon), I daemonize it and start it running (saving the PID in a file).

When starting the script with another command-line option (eg: --stop), it finds the PID file and kills the instance.

In a sense, I'd like to combine the wrapper method given in the examples and the script it runs, but the wrapper example seems to absorb command-line options, and I want to call those functions from within Ruby instead.

Suggestions?

-Payton

--

ara [dot] t [dot] howard [at] noaa [dot] gov
all happiness comes from the desire for others to be happy. all misery
comes from the desire for oneself to be happy.
-- bodhicaryavatara

===============================================================================

Well, I did it myself anyway. Hope it's useful:
http://rubyforge.org/projects/pidify/

-Payton

Payton Swick wrote:

···

Has anyone used the Daemons library (http://daemons.rubyforge.org/\) extensively? I'm trying to do something that doesn't appear covered by the examples, and I'm wondering if I need to reinvent the wheel.

What I'd like to do is to easily add functionality to my script similar to the way that fetchmail works, ie:

When starting the script with one command-line option (eg: --daemon), I daemonize it and start it running (saving the PID in a file).

When starting the script with another command-line option (eg: --stop), it finds the PID file and kills the instance.

In a sense, I'd like to combine the wrapper method given in the examples and the script it runs, but the wrapper example seems to absorb command-line options, and I want to call those functions from within Ruby instead.

Suggestions?

-Payton

it does look useful. few comments/suggestions though

   - since it's *nix specify anyhow you might want to use O_EXCL when opening
     the file since otherwise there is a race condition to create the file

   - on the same page using File#flock with File::LOCK_EX when writing and
     File::LOCK_SH for writing and reading (with File::LOCK_NB of course) will
     avoid the race conditions and possibibity of reading a half-baked file

   - escalation of signals with a pause between is useful for killing processes
     since many trap signit and most do not die immediately. something like

       def maim(pid, signals=%w(SIGTERM SIGQUIT SIGKILL), suspend=4)
         pid = Integer("#{ pid }")
         signals = [ signals ].flatten.map{|sig| "#{ sig }"}
         suspend = Integer("#{ suspend }")

         existed = false
         sigs.each do |sig|
           begin
             Process::kill(sig, pid)
             existed = true
           rescue Errno::ESRCH
             return(existed ? true : nil)
           end
           return true unless alive?(pid)
           sleep suspend
           return true unless alive?(pid)
         end
         return(not alive?(pid))
       end

     which makes sure the process is actually dead prevents removing the
     pidfile in cases where the Process::kill didn't work

   - the alive function above can be something like this

       def alive pid
         pid = Integer("#{ pid }")
         begin
           Process::kill 0, pid
           true
         rescue Errno::ESRCH
           false
         end
       end

cheers.

-a

···

On Tue, 10 Jan 2006, Payton Swick wrote:

Well, I did it myself anyway. Hope it's useful:
http://rubyforge.org/projects/pidify/

-Payton

--

ara [dot] t [dot] howard [at] noaa [dot] gov
strong and healthy,
who thinks of sickness until it strikes like lightning?
preoccupied with the world,
who thinks of death, until it arrives like thunder?
-- milarepa

===============================================================================

Instead of putting this logic in (every) application, have you considered
using daemontools? http://cr.yp.to/daemontools.html

Personally, I think pid files are evil.

···

On Tue, Jan 10, 2006 at 11:53:08PM +0900, Payton Swick wrote:

Well, I did it myself anyway. Hope it's useful:
http://rubyforge.org/projects/pidify/

--
Jos Backus
jos at catnook.com

it certainly is :slight_smile:

i was working on something similar, but decided to use your wheel
instead of re-inventing my own.

···

Payton Swick wrote on 1/10/2006 9:53 AM: > Well, I did it myself anyway. Hope it's useful:

http://rubyforge.org/projects/pidify/

--
http://home.cogeco.ca/~tsummerfelt1
telnet://ventedspleen.dyndns.org

Would this be correct usage of the file, then?

    def pid
       dpid = nil
       File.open(pid_file, File::RDONLY) { |file| dpid = file.gets.chomp if file.flock(File::LOCK_SH|File::LOCK_NB); file.flock(File::LOCK_UN) } if pid_exists?
       dpid.to_i
     end

     def save_pid
       File.open(pid_file, File::CREAT|File::EXCL|File::WRONLY) { |file| file.puts $$ if file.flock(File::LOCK_EX); file.flock(File::LOCK_UN) }
     end

-Payton

···

ara.t.howard@noaa.gov wrote:

On Tue, 10 Jan 2006, Payton Swick wrote:

Well, I did it myself anyway. Hope it's useful:
http://rubyforge.org/projects/pidify/

-Payton

it does look useful. few comments/suggestions though

  - since it's *nix specify anyhow you might want to use O_EXCL when opening
    the file since otherwise there is a race condition to create the file

  - on the same page using File#flock with File::LOCK_EX when writing and
    File::LOCK_SH for writing and reading (with File::LOCK_NB of course) will
    avoid the race conditions and possibibity of reading a half-baked file

  - escalation of signals with a pause between is useful for killing processes
    since many trap signit and most do not die immediately. something like

      def maim(pid, signals=%w(SIGTERM SIGQUIT SIGKILL), suspend=4)
        pid = Integer("#{ pid }")
        signals = [ signals ].flatten.map{|sig| "#{ sig }"}
        suspend = Integer("#{ suspend }")

        existed = false
        sigs.each do |sig|
          begin
            Process::kill(sig, pid)
            existed = true
          rescue Errno::ESRCH
            return(existed ? true : nil)
          end
          return true unless alive?(pid)
          sleep suspend
          return true unless alive?(pid)
        end
        return(not alive?(pid))
      end

    which makes sure the process is actually dead prevents removing the
    pidfile in cases where the Process::kill didn't work

  - the alive function above can be something like this

      def alive pid
        pid = Integer("#{ pid }")
        begin
          Process::kill 0, pid
          true
        rescue Errno::ESRCH
          false
        end
      end

cheers.

-a

more or less. it can still get slippery though. consider:

   def pid
     dpid = nil
     File.open(pid_file, File::RDONLY) { |file| dpid = file.gets.chomp if file.flock(File::LOCK_SH|File::LOCK_NB); file.flock(File::LOCK_UN) } if pid_exists?
     dpid.to_i
   end

       - if the file does not exists this returns nil.to_i, which is 0

       - races still exist:

           0) process a: reader opens existing pidfile

           1) process b: removes pidfile

           2) process a: read lock obtained, pid read

         this works because:

           harp:~ > irb
           irb(main):001:0> open('pid','w'){|f| f.write Process::pid}
           => 4
           irb(main):002:0> f = open 'pid'
           => #<File:pid>
           irb(main):003:0> File::unlink 'pid'
           => 1
           irb(main):004:0> File::exists? 'pid'
           => false
           irb(main):005:0> f.flock File::LOCK_SH|File::LOCK_NB
           => 0
           irb(main):006:0> f.read
           => "4000"

         i think this can be solved by only removing the file while holding a
         write lock.

   def save_pid
     File.open(pid_file, File::CREAT|File::EXCL|File::WRONLY) do |file|
       file.puts $$ if file.flock(File::LOCK_EX); file.flock(File::LOCK_UN)
     end
   end

         - on some filesystems File::EXCL does not work. on those systems two
           process can succeed in opening the file for writing, waiting for the
           lock, and then writing the pid with the second clobbering the first.
           the open man page details this behaviour. the only solution is to
           use an operation which atomically creates a file. the only one i
           know of is 'link'. i don't think this is worth dealing with though
           since /var/run is probably on a fs for which File::EXCL works...

cheers.

-a

···

On Wed, 11 Jan 2006, Payton Swick wrote:

Would this be correct usage of the file, then?

  def pid
     dpid = nil
     File.open(pid_file, File::RDONLY) { |file| dpid = file.gets.chomp if file.flock(File::LOCK_SH|File::LOCK_NB); file.flock(File::LOCK_UN) } if pid_exists?
     dpid.to_i
   end

   def save_pid
     File.open(pid_file, File::CREAT|File::EXCL|File::WRONLY) { |file| file.puts $$ if file.flock(File::LOCK_EX); file.flock(File::LOCK_UN) }
   end

--
strong and healthy, who thinks of sickness until it strikes like lightning?
preoccupied with the world, who thinks of death, until it arrives like
thunder? -- milarepa