require 'daemons/pidfile' require 'daemons/pidmem' require 'daemons/change_privilege' require 'daemons/daemonize' require 'timeout' module Daemons class Application attr_accessor :app_argv attr_accessor :controller_argv # the Pid instance belonging to this application attr_reader :pid # the ApplicationGroup the application belongs to attr_reader :group # my private options attr_reader :options SIGNAL = (RUBY_PLATFORM =~ /win32/ ? 'KILL' : 'TERM') def initialize(group, add_options = {}, pid = nil) @group = group @options = group.options.dup @options.update(add_options) @dir_mode = @dir = @script = nil @force_kill_waittime = @options[:force_kill_waittime] || 20 unless @pid = pid if @options[:no_pidfiles] @pid = PidMem.new elsif dir = pidfile_dir @pid = PidFile.new(dir, @group.app_name, @group.multiple) else @pid = PidMem.new end end end def change_privilege user = options[:user] group = options[:group] CurrentProcess.change_privilege(user, group) if user end def script @script || @group.script end def pidfile_dir Pid.dir(@dir_mode || @group.dir_mode, @dir || @group.dir, @script || @group.script) end def logdir logdir = options[:log_dir] unless logdir logdir = options[:dir_mode] == :system ? '/var/log' : pidfile_dir end logdir end def output_logfile (options[:log_output] && logdir) ? File.join(logdir, @group.app_name + '.output') : nil end def logfile logdir ? File.join(logdir, @group.app_name + '.log') : nil end # this function is only used to daemonize the currently running process (Daemons.daemonize) def start_none unless options[:ontop] Daemonize.daemonize(output_logfile, @group.app_name) else Daemonize.simulate(output_logfile) end @pid.pid = Process.pid # We need this to remove the pid-file if the applications exits by itself. # Note that at_text will only be run if the applications exits by calling # exit, and not if it calls exit! (so please don't call exit! # in your application! # at_exit { begin; @pid.cleanup; rescue ::Exception; end # If the option :backtrace is used and the application did exit by itself # create a exception log. if options[:backtrace] and not options[:ontop] and not $daemons_sigterm begin; exception_log(); rescue ::Exception; end end } # This part is needed to remove the pid-file if the application is killed by # daemons or manually by the user. # Note that the applications is not supposed to overwrite the signal handler for # 'TERM'. # trap(SIGNAL) { begin; @pid.cleanup; rescue ::Exception; end $daemons_sigterm = true if options[:hard_exit] exit! else exit end } end def start_exec if options[:backtrace] puts "option :backtrace is not supported with :mode => :exec, ignoring" end unless options[:ontop] Daemonize.daemonize(output_logfile, @group.app_name) else Daemonize.simulate(output_logfile) end # note that we cannot remove the pid file if we run in :ontop mode (i.e. 'ruby ctrl_exec.rb run') @pid.pid = Process.pid ENV['DAEMONS_ARGV'] = @controller_argv.join(' ') # haven't tested yet if this is really passed to the exec'd process... started() Kernel.exec(script(), *(@app_argv || [])) end def start_load unless options[:ontop] Daemonize.daemonize(output_logfile, @group.app_name) else Daemonize.simulate(output_logfile) end @pid.pid = Process.pid # We need this to remove the pid-file if the applications exits by itself. # Note that at_exit will only be run if the applications exits by calling # exit, and not if it calls exit! (so please don't call exit! # in your application! # at_exit { begin; @pid.cleanup; rescue ::Exception; end # If the option :backtrace is used and the application did exit by itself # create a exception log. if options[:backtrace] and not options[:ontop] and not $daemons_sigterm begin; exception_log(); rescue ::Exception; end end } # This part is needed to remove the pid-file if the application is killed by # daemons or manually by the user. # Note that the applications is not supposed to overwrite the signal handler for # 'TERM'. # $daemons_stop_proc = options[:stop_proc] trap(SIGNAL) { begin if $daemons_stop_proc $daemons_stop_proc.call end rescue ::Exception end begin; @pid.cleanup; rescue ::Exception; end $daemons_sigterm = true if options[:hard_exit] exit! else exit end } # Now we really start the script... $DAEMONS_ARGV = @controller_argv ENV['DAEMONS_ARGV'] = @controller_argv.join(' ') ARGV.clear ARGV.concat @app_argv if @app_argv started() # TODO: begin - rescue - end around this and exception logging load script() end def start_proc return unless p = options[:proc] myproc = proc do @pid.pid = Process.pid # We need this to remove the pid-file if the applications exits by itself. # Note that at_text will only be run if the applications exits by calling # exit, and not if it calls exit! (so please don't call exit! # in your application! # at_exit { begin; @pid.cleanup; rescue ::Exception; end # If the option :backtrace is used and the application did exit by itself # create a exception log. if options[:backtrace] and not options[:ontop] and not $daemons_sigterm begin; exception_log(); rescue ::Exception; end end } # This part is needed to remove the pid-file if the application is killed by # daemons or manually by the user. # Note that the applications is not supposed to overwrite the signal handler for # 'TERM'. # $daemons_stop_proc = options[:stop_proc] trap(SIGNAL) { begin if $daemons_stop_proc $daemons_stop_proc.call end rescue ::Exception end begin; @pid.cleanup; rescue ::Exception; end $daemons_sigterm = true if options[:hard_exit] exit! else exit end } started() p.call() end unless options[:ontop] Daemonize.call_as_daemon(myproc, output_logfile, @group.app_name) else Daemonize.simulate(output_logfile) myproc.call # why did we use this?? # Thread.new(&options[:proc]) # why did we use the code below?? # unless pid = Process.fork # @pid.pid = pid # Daemonize.simulate(logfile) # options[:proc].call # exit # else # Process.detach(@pid.pid) # end end end def start change_privilege @group.create_monitor(@group.applications[0] || self) unless options[:ontop] # we don't monitor applications in the foreground case options[:mode] when :none # this is only used to daemonize the currently running process start_none when :exec start_exec when :load start_load when :proc start_proc else start_load end end def started if pid = @pid.pid puts "#{self.group.app_name}: process with pid #{pid} started." STDOUT.flush end end # def run # if @group.controller.options[:exec] # run_via_exec() # else # run_via_load() # end # end # # def run_via_exec # # end # # def run_via_load # # end def reload if @pid.pid == 0 zap start else begin Process.kill('HUP', @pid.pid) rescue # ignore end end end # This is a nice little function for debugging purposes: # In case a multi-threaded ruby script exits due to an uncaught exception # it may be difficult to find out where the exception came from because # one cannot catch exceptions that are thrown in threads other than the main # thread. # # This function searches for all exceptions in memory and outputs them to STDERR # (if it is connected) and to a log file in the pid-file directory. # def exception_log return unless logfile require 'logger' l_file = Logger.new(logfile) # the code below finds the last exception e = nil ObjectSpace.each_object {|o| if ::Exception === o e = o end } l_file.info "*** below you find the most recent exception thrown, this will be likely (but not certainly) the exception that made the application exit abnormally ***" l_file.error e l_file.info "*** below you find all exception objects found in memory, some of them may have been thrown in your application, others may just be in memory because they are standard exceptions ***" # this code logs every exception found in memory ObjectSpace.each_object {|o| if ::Exception === o l_file.error o end } l_file.close end def stop(no_wait = false) if not running? self.zap return end pid = @pid.pid # Catch errors when trying to kill a process that doesn't # exist. This happens when the process quits and hasn't been # restarted by the monitor yet. By catching the error, we allow the # pid file clean-up to occur. begin Process.kill(SIGNAL, pid) rescue Errno::ESRCH => e puts "#{e} #{pid}" puts "deleting pid-file." end if not no_wait if @force_kill_waittime > 0 puts "#{self.group.app_name}: trying to stop process with pid #{pid}..." STDOUT.flush begin Timeout::timeout(@force_kill_waittime) { while Pid.running?(pid) sleep(0.2) end } rescue Timeout::Error puts "#{self.group.app_name}: process with pid #{pid} won't stop, we forcefully kill it..." STDOUT.flush begin Process.kill('KILL', pid) rescue Errno::ESRCH end begin Timeout::timeout(20) { while Pid.running?(pid) sleep(1) end } rescue Timeout::Error puts "#{self.group.app_name}: unable to forcefully kill process with pid #{pid}." STDOUT.flush end end end end sleep(0.1) unless Pid.running?(pid) # We try to remove the pid-files by ourselves, in case the application # didn't clean it up. begin; @pid.cleanup; rescue ::Exception; end puts "#{self.group.app_name}: process with pid #{pid} successfully stopped." STDOUT.flush end end def zap @pid.cleanup end def zap! begin; @pid.cleanup; rescue ::Exception; end end def show_status running = self.running? puts "#{self.group.app_name}: #{running ? '' : 'not '}running#{(running and @pid.exist?) ? ' [pid ' + @pid.pid.to_s + ']' : ''}#{(@pid.exist? and not running) ? ' (but pid-file exists: ' + @pid.pid.to_s + ')' : ''}" end # This function implements a (probably too simle) method to detect # whether the program with the pid found in the pid-file is still running. # It just searches for the pid in the output of ps ax, which # is probably not a good idea in some cases. # Alternatives would be to use a direct access method the unix process control # system. # def running? if @pid.exist? return Pid.running?(@pid.pid) end return false end end end