lib/daemons/daemonize.rb
module Daemonize
# Try to fork if at all possible retrying every 5 sec if the
# maximum process limit for the system has been reached
def safefork
tryagain = true
while tryagain
tryagain = false
begin
if pid = fork
return pid
end
rescue Errno::EWOULDBLOCK
sleep 5
tryagain = true
end
end
end
module_function :safefork
# Simulate the daemonization process (:ontop mode)
# NOTE: STDOUT and STDERR will not be redirected to the logfile,
# because in :ontop mode, we normally want to see the output
def simulate(logfile_name = nil, app_name = nil)
$0 = app_name if app_name
# Release old working directory
Dir.chdir '/'
close_io
# Free STDIN and point it to somewhere sensible
begin; STDIN.reopen '/dev/null'; rescue ::Exception; end
# Split rand streams between spawning and daemonized process
srand
end
module_function :simulate
# Call a given block as a daemon
def call_as_daemon(block, logfile_name = nil, app_name = nil)
# we use a pipe to return the PID of the daemon
rd, wr = IO.pipe
if tmppid = safefork
# in the parent
wr.close
pid = rd.read.to_i
rd.close
Process.waitpid(tmppid)
return pid
else
# in the child
rd.close
# Detach from the controlling terminal
unless sess_id = Process.setsid
fail Daemons.RuntimeException.new('cannot detach from controlling terminal')
end
# Prevent the possibility of acquiring a controlling terminal
trap 'SIGHUP', 'IGNORE'
exit if pid = safefork
wr.write Process.pid
wr.close
$0 = app_name if app_name
# Release old working directory
Dir.chdir '/'
close_io
redirect_io(logfile_name)
# Split rand streams between spawning and daemonized process
srand
block.call
exit
end
end
module_function :call_as_daemon
# Transform the current process into a daemon
def daemonize(logfile_name = nil, app_name = nil)
# Fork and exit from the parent
safefork && exit
# Detach from the controlling terminal
unless sess_id = Process.setsid
fail Daemons.RuntimeException.new('cannot detach from controlling terminal')
end
# Prevent the possibility of acquiring a controlling terminal
trap 'SIGHUP', 'IGNORE'
exit if pid = safefork
$0 = app_name if app_name
# Release old working directory
Dir.chdir '/'
close_io
redirect_io(logfile_name)
# Split rand streams between spawning and daemonized process
srand
sess_id
end
module_function :daemonize
def close_io
# Make sure all input/output streams are closed
# Part I: close all IO objects (except for STDIN/STDOUT/STDERR)
ObjectSpace.each_object(IO) do |io|
unless [STDIN, STDOUT, STDERR].include?(io)
io.close rescue nil
end
end
# Make sure all input/output streams are closed
# Part II: close all file decriptors (except for STDIN/STDOUT/STDERR)
3.upto(8192) do |i|
IO.for_fd(i).close rescue nil
end
end
module_function :close_io
# Free STDIN/STDOUT/STDERR file descriptors and
# point them somewhere sensible
def redirect_io(logfile_name)
begin; STDIN.reopen '/dev/null'; rescue ::Exception; end
if logfile_name
begin
STDOUT.reopen logfile_name, 'a'
File.chmod(0644, logfile_name)
STDOUT.sync = true
rescue ::Exception
begin; STDOUT.reopen '/dev/null'; rescue ::Exception; end
end
else
begin; STDOUT.reopen '/dev/null'; rescue ::Exception; end
end
begin; STDERR.reopen STDOUT; rescue ::Exception; end
STDERR.sync = true
end
module_function :redirect_io
end