require'daemons/pidfile'require'daemons/pidmem'require'daemons/change_privilege'require'daemons/daemonize'require'daemons/exceptions'require'daemons/reporter'require'timeout'moduleDaemonsclassApplicationattr_accessor:app_argvattr_accessor:controller_argv# the Pid instance belonging to this applicationattr_reader:pid# the ApplicationGroup the application belongs toattr_reader:group# my private optionsattr_reader:optionsSIGNAL=(RUBY_PLATFORM=~/win32/?'KILL':'TERM')definitialize(group,add_options={},pid=nil)@group=group@options=group.options.dup@options.update(add_options)['dir','log_dir','logfilename','output_logfilename'].eachdo|k|@options[k]=File.expand_path(@options[k])if@options.key?(k)end@dir_mode=@dir=@script=nil@force_kill_waittime=@options[:force_kill_waittime]||20@signals_and_waits=parse_signals_and_waits(@options[:signals_and_waits])@show_status_callback=method(:default_show_status)@report=Reporter.new(@options)unless@pid=pidif@options[:no_pidfiles]@pid=PidMem.newelsifdir=pidfile_dir@pid=PidFile.new(dir,@group.app_name,@group.multiple,@options[:pid_delimiter])else@pid=PidMem.newendendenddefshow_status_callback=(function)@show_status_callback=iffunction.respond_to?(:call)functionelsemethod(function)endenddefchange_privilegeuser=options[:user]group=options[:group]ifuser@report.changing_process_privilege(user,group)CurrentProcess.change_privilege(user,group)endenddefscript@scriptorgroup.scriptenddefpidfile_dirPid.dirdir_mode,dir,scriptenddeflogdiroptions[:log_dir]oroptions[:dir_mode]==:system?'/var/log':pidfile_direnddefoutput_logfilenameoptions[:output_logfilename]or"#{@group.app_name}.output"enddefoutput_logfileiflog_output_syslog?'SYSLOG'elsiflog_output?File.joinlogdir,output_logfilenameendenddeflogfilenameoptions[:logfilename]or"#{@group.app_name}.log"enddeflogfileiflogdirFile.joinlogdir,logfilenameendend# this function is only used to daemonize the currently running process (Daemons.daemonize)defstart_noneunlessoptions[:ontop]Daemonize.daemonize(output_logfile,@group.app_name)elseDaemonize.simulate(output_logfile)end@pid.pid=Process.pid# We need this to remove the pid-file if the applications exits by itself.# Note that <tt>at_text</tt> will only be run if the applications exits by calling# <tt>exit</tt>, and not if it calls <tt>exit!</tt> (so please don't call <tt>exit!</tt># in your application!#at_exitdobegin;@pid.cleanup;rescue::Exception;end# If the option <tt>:backtrace</tt> is used and the application did exit by itself# create a exception log.ifoptions[:backtrace]&&!options[:ontop]&&!$daemons_sigtermbegin;exception_log;rescue::Exception;endendend# 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)dobegin;@pid.cleanup;rescue::Exception;end$daemons_sigterm=trueifoptions[:hard_exit]exit!elseexitendendenddefstart_execifoptions[:backtrace]@report.backtrace_not_supportedendunlessoptions[:ontop]Daemonize.daemonize(output_logfile,@group.app_name)elseDaemonize.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.pidENV['DAEMONS_ARGV']=@controller_argv.join(' ')startedKernel.exec(script,*(@app_argv||[]))enddefstart_loadunlessoptions[:ontop]Daemonize.daemonize(output_logfile,@group.app_name)elseDaemonize.simulate(output_logfile)end@pid.pid=Process.pid# We need this to remove the pid-file if the applications exits by itself.# Note that <tt>at_exit</tt> will only be run if the applications exits by calling# <tt>exit</tt>, and not if it calls <tt>exit!</tt> (so please don't call <tt>exit!</tt># in your application!#at_exitdobegin;@pid.cleanup;rescue::Exception;end# If the option <tt>:backtrace</tt> is used and the application did exit by itself# create a exception log.ifoptions[:backtrace]&&!options[:ontop]&&!$daemons_sigtermbegin;exception_log;rescue::Exception;endendend# 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)dobeginif$daemons_stop_proc$daemons_stop_proc.callendrescue::Exceptionendbegin;@pid.cleanup;rescue::Exception;end$daemons_sigterm=trueifoptions[:hard_exit]exit!elseexitendend# Now we really start the script...$DAEMONS_ARGV=@controller_argvENV['DAEMONS_ARGV']=@controller_argv.join(' ')ARGV.clearARGV.concat@app_argvif@app_argvstarted# TODO: exception loggingloadscriptenddefstart_procreturnunlessp=options[:proc]myproc=procdo# We need this to remove the pid-file if the applications exits by itself.# Note that <tt>at_text</tt> will only be run if the applications exits by calling# <tt>exit</tt>, and not if it calls <tt>exit!</tt> (so please don't call <tt>exit!</tt># in your application!#at_exitdobegin;@pid.cleanup;rescue::Exception;end# If the option <tt>:backtrace</tt> is used and the application did exit by itself# create a exception log.ifoptions[:backtrace]&&!options[:ontop]&&!$daemons_sigtermbegin;exception_log;rescue::Exception;endendend# 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)dobeginif$daemons_stop_proc$daemons_stop_proc.callendrescue::Exceptionendbegin;@pid.cleanup;rescue::Exception;end$daemons_sigterm=trueifoptions[:hard_exit]exit!elseexitendendp.callendunlessoptions[:ontop]@pid.pid=Daemonize.call_as_daemon(myproc,output_logfile,@group.app_name)elseDaemonize.simulate(output_logfile)myproc.callendstartedenddefstart(restart=false)change_privilegeunlessrestart@group.create_monitor(self)unlessoptions[:ontop]# we don't monitor applications in the foregroundendcaseoptions[:mode]when:none# this is only used to daemonize the currently running processstart_nonewhen:execstart_execwhen:loadstart_loadwhen:procstart_procelsestart_loadendenddefstartedifpid=@pid.pid@report.process_started(group.app_name,pid)endenddefreloadif@pid.pid==0zapstartelsebeginProcess.kill('HUP',@pid.pid)rescue# ignoreendendend# 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.#defexception_logreturnunlesslogfilerequire'logger'l_file=Logger.new(logfile)# the code below finds the last exceptione=nilObjectSpace.each_objectdo|o|if::Exception===oe=oendendl_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.errorel_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 memoryObjectSpace.each_objectdo|o|if::Exception===ol_file.erroroendendl_file.closeenddefstop(no_wait=false)unlessrunning?zapreturnend# confusing: pid is also a attribute_readerpid=@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.beginwait_and_retry_kill_harder(pid,@signals_and_waits,no_wait)rescueErrno::ESRCH=>e@report.output_message("#{e}#{pid}")@report.output_message('deleting pid-file.')endsleep(0.1)unlessPid.running?(pid)# We try to remove the pid-files by ourselves, in case the application# didn't clean it up.zap!@report.stopped_process(group.app_name,pid)endend# @param Hash remaing_signals# @param Boolean no_wait Send first Signal and returndefwait_and_retry_kill_harder(pid,remaining_signals,no_wait=false)sig_wait=remaining_signals.shiftsig=sig_wait[:sig]wait=sig_wait[:wait]Process.kill(sig,pid)returnifno_wait||!wait.positive?@report.stopping_process(group.app_name,pid,sig,wait)beginTimeout.timeout(wait,TimeoutError)dosleep(0.2)whilePid.running?(pid)endrescueTimeoutErrorifremaining_signals.any?wait_and_retry_kill_harder(pid,remaining_signals)else@report.cannot_stop_process(group.app_name,pid)endendenddefzap@pid.zapenddefzap!begin;@pid.zap;rescue::Exception;endenddefshow_status@show_status_callback.call(self)enddefdefault_show_status(daemon=self)running=daemon.running?@report.status(group.app_name,running,daemon.pid.exist?,daemon.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 <tt>ps ax</tt>, which# is probably not a good idea in some cases.# Alternatives would be to use a direct access method the unix process control# system.#defrunning?@pid.exist?andPid.running?@pid.pidendprivatedeflog_output?options[:log_output]&&logdirenddeflog_output_syslog?options[:log_output_syslog]enddefdir_mode@dir_modeorgroup.dir_modeenddefdir@dirorgroup.direnddefparse_signals_and_waits(argv)unlessargvreturn[{sig: 'TERM',wait: @force_kill_waittime},{sig: 'KILL',wait: 20}]endargv.split('|').collect{|part|splitted=part.split(':');{sig: splitted[0],wait: splitted[1].to_i}}endendend