Create a Daemon with Double-Fork in Ruby

Create a daemon with double-fork in Ruby

According to Stevens's Advanced Programming in the UNIX Environment chapter 13, this is the procedure to make a well-behaved Unix daemon:

  1. Fork and have the parent exit. This makes the shell or boot script think the command is done. Also, the child process is guaranteed not to be a process group leader (a prerequisite for setsid next)
  2. Call setsid to create a new session. This does three things:

    1. The process becomes a session leader of a new session
    2. The process becomes the process group leader of a new process group
    3. The process has no controlling terminal
  3. Optionally fork again and have the parent exit. This guarantes that the daemon is not a session leader nor can it acquire a controlling terminal (under SVR4)
  4. Change the current working directory to / to avoid interfering with mounting and unmounting
  5. Set file mode creation mask to 000 to allow creation of files with any required permission later.
  6. Close unneeded file descriptors inherited from the parent (there is no controlling terminal anyway): stdout, stderr, and stdin.

Nowadays there is a file to track the PID which is used heavily by Linux distribution boot scripts. Be sure to write out the PID of the grandchild, either the return value of the second fork (step 3) or the value of getpid() after step 3.

Here is a Ruby implementation, mostly translated from the book, but with the double-fork and writing out the daemon PID.

# Example double-forking Unix daemon initializer.

raise 'Must run as root' if Process.euid != 0

raise 'First fork failed' if (pid = fork) == -1
exit unless pid.nil?

Process.setsid
raise 'Second fork failed' if (pid = fork) == -1
exit unless pid.nil?
puts "Daemon pid: #{Process.pid}" # Or save it somewhere, etc.

Dir.chdir '/'
File.umask 0000

STDIN.reopen '/dev/null'
STDOUT.reopen '/dev/null', 'a'
STDERR.reopen STDOUT

background/daemon process

All the answers above fail to actually show how easy it is to do this:

# Daemonize the process and stay in the current directory
Process.daemon(true)

loop do
pid = Process.fork do
# Do something funky
end

Process.waitpid(pid)

# Reduce CPU usage
sleep(0.1)
end

What is the reason for performing a double fork when creating a daemon?

Looking at the code referenced in the question, the justification is:

Fork a second child and exit immediately to prevent zombies. This
causes the second child process to be orphaned, making the init
process responsible for its cleanup. And, since the first child is
a session leader without a controlling terminal, it's possible for
it to acquire one by opening a terminal in the future (System V-
based systems). This second fork guarantees that the child is no
longer a session leader, preventing the daemon from ever acquiring
a controlling terminal.

So it is to ensure that the daemon is re-parented onto init (just in case the process kicking off the daemon is long lived), and removes any chance of the daemon reacquiring a controlling tty. So if neither of these cases apply, then one fork should be sufficient. "Unix Network Programming - Stevens" has a good section on this.

Daemon can still print to stdout?

Yes I believe this is expected. Calling Process.setsid does not close any open file descriptors, you will need to manually reopen them with /dev/null, e.g.:

io = File.open("/dev/null", "w+")

exit if fork
Process.setsid
exit if fork

$stdin.reopen(io)
$stdout.reopen(io)
$stderr.reopen(io)

Or, if you care about the output, you could reopen stdout and stderr with log files.

I would also recommend changing the working directory to / to avoid the daemon running in a non-existent directory:

Dir.chdir("/")

Ruby daemon process to keep objects alive for transient Ruby instances

You're probably looking for something like memcache (Gem) or redis (Gem).

You may also be interested in redis-objects.

Double fork and stdin

How about a fifo? In linux, you can use the mkfifo command:

$ mkfifo /tmp/mypipe

Then you can reopen STDIN on that pipe:

STDIN.reopen '/tmp/mypipe'
# Do read-y things

Anything else can write to that pipe:

$ echo "roflcopter" > /tmp/mypipe

allowing that data to be read by the ruby process.

(Update) Caveat with blocking

Since fifos block until there's a read and write (e.g. a read is blocked unless there's a write, and vice-versa), it's best handled with multiple threads. One thread should do the reading, passing the data to a queue, and another should handle that input. Here's an example of that situation:

require 'thread'

input = Queue.new
threads = []

# Read from the fifo and add to an input queue (glorified array)
threads << Thread.new(input) do |ip|
STDIN.reopen 'mypipe'
loop do
if line = STDIN.gets
puts "Read: #{line}"
ip.push line
end
end
end

# Handle the input passed by the reader thread
threads << Thread.new(input) do |ip|
loop do
puts "Ouput: #{ip.pop}"
end
end

threads.map(&:join)

What's the purpose of daemonize like this?

The process becomes detached from the terminal so that when the user that started it quits his session, the process remains running.

Daemonizing is not only about the fork, it involves calling setsid(2) in the child process that detaches the process from the controlling terminal. If you run ps you can see such processes marked with a ? in the tty column.

Also, when you start a daemon it returns the control to the shell right after the fork, rather than the shell having to wait for the process to terminate.

Permanent daemon for quering a web resource

Finaly i found a cool solution for my problem:

I use the god gem -> http://god.rubyforge.org/

with a bash script (link) for starting / stopping a simple rake task (with an infinite loop in it).

Now it works fine and i have even some monitoring with god running that ensures that the rake task runs ok.



Related Topics



Leave a reply



Submit