class Servolux::Daemon
daemon.startup
daemon = Servolux::Daemon.new(:server => server, :log_file => ‘timestamps.txt’)
end
def run() file.puts Time.now; end
def file() @fd ||= File.open(‘timestamps.txt’, ‘w’); end
class << server
server = Servolux::Server.new(‘TimeStamp’, :interval => 60)
So, it’s not really a “good” example, but it will work.
This is a simple Ruby server that prints the time to a file every minute.
==== Good Example
daemon.startup #=> raises StartupError
)
:startup_command => ‘/usr/bin/no-command-by-this-name’
:pid_file => ‘/dev/null’,
:name => ‘Bad Example’,
daemon = Servolux::Daemon.new(
back to the parent which gets wrapped in a StartupError
system. The daemon process will send an Errno::ENOENT through the pipe
command “/usr/bin/no-command-by-this-name” cannot be found on the file
This is a bad example. The daemon will not start because the startup
==== Bad Example
== Examples
returning when this signal fails – i.e. then the daemon process has died.
This is determined by attempting to signal the daemon process PID and then
Again, the Daemon instance will wait till the daemon process shuts down.
signal to the daemon process.
command can be used, or the Daemon instance will send an INT or TERM
Shutting down the daemon process is a little simpler. An external shutdown
process is up and running.
watched for a specific pattern; this pattern signals that the daemon
daemon process is updating the log file. Furthermore, the log file can be
change in size and mtime. This lets the Daemon instance know that the
If a log file is given to the Daemon instance, then it is monitored for a
process.
daemon starts. This is determined by sending a signal to the daemon
If no errors are passed up the pipe, the parent process waits till the
errors are wrapped in a StartupError and then raised in the parent.
daemon process are marshalled through the pipe back to the parent. These
check if the daemon is alive. Along with the PID, any errors from the
of the daemon is sent to the parent through this pipe. The PID is used to
The Daemon class opens a pipe between the parent and the daemon. The PID
difficult to know if the daemon started properly.
this separation between the parent process and the daemon process, it is
working directory and standard in/out/error file descriptors. Because of
child as a session leader, forking again, and detaching from the current
Starting a daemon process involves forking a child process, setting the
and can be shutdown gracefully.
class encapsulates some best practices to ensure daemons startup properly
of processes are notoriously difficult to setup correctly. This Daemon
detached from a TTY – i.e. it is not tied to a user session. These types
A daemon process is a long running process on a UNIX system that is
== Details
processes from Ruby.
The Daemon takes care of the work of creating and managing daemon
== Synopsis
def alive?
-
(Boolean)
-
def alive? pid_file.alive? end
def exec( *args )
def exec( *args ) logger.debug "Calling: exec(*#{args.inspect})" skip = [STDIN, STDOUT, STDERR] skip << @piper.socket if @piper ObjectSpace.each_object(IO) { |io| next if skip.include? io io.close unless io.closed? } before_exec.call if before_exec.respond_to? :call Kernel.exec(*args) end
def initialize( opts = {} )
- Yield: - Block used to configure the daemon instance
Options Hash:
(**opts)
-
:before_exec
(Proc, lambda
) -- -
:after_fork
(Proc, lambda
) -- -
:look_for
(String, Regexp
) -- -
:log_file
(String
) -- -
:shutdown_command
(Numeric, String, Array
) --, Proc, Method, Servolux::Server -
:noclose
(Boolean
) -- -
:nochdir
(Boolean
) -- -
:timeout
(Numeric
) -- -
:startup_command
(String, Array
) --, Proc, Method, Servolux::Server -
:pid_file
(String
) -- -
:logger
(Logger
) -- -
:name
(String
) --
def initialize( opts = {} ) @piper = nil @logfile_reader = nil @pid_file = nil self.name = opts.fetch(:name, nil) self.logger = opts.fetch(:logger, Servolux::NullLogger()) self.startup_command = opts.fetch(:server, nil) || opts.fetch(:startup_command, nil) self.shutdown_command = opts.fetch(:shutdown_command, nil) self.timeout = opts.fetch(:timeout, 30) self.nochdir = opts.fetch(:nochdir, false) self.noclose = opts.fetch(:noclose, false) self.log_file = opts.fetch(:log_file, nil) self.look_for = opts.fetch(:look_for, nil) self.after_fork = opts.fetch(:after_fork, nil) self.before_exec = opts.fetch(:before_exec, nil) self.pid_file = opts.fetch(:pid_file, name) if pid_file.nil? yield self if block_given? ary = %w[name logger pid_file startup_command].map { |var| self.send(var).nil? ? var : nil }.compact raise Error, "These variables are required: #{ary.join(', ')}." unless ary.empty? end
def kill( signal = 'INT' )
-
(Daemon)
- self
Parameters:
-
signal
(String, Integer
) -- The kill signal to send to the daemon
def kill( signal = 'INT' ) pid_file.kill signal end
def log_file=( filename )
-
filename
(String
) -- The name of the log file to monitor
def log_file=( filename ) return if filename.nil? @logfile_reader ||= LogfileReader.new @logfile_reader.filename = filename end
def look_for=( val )
-
val
(String, Regexp
) -- The phrase in the log file to search for
def look_for=( val ) return if val.nil? @logfile_reader ||= LogfileReader.new @logfile_reader.look_for = val end
def pid_file=( value )
value - The PID file name or a PidFile instance.
instance.
it is used. If a name is given, then that name is used to create a PifFile
Set the PID file to the given `value`. If a PidFile instance is given, then
def pid_file=( value ) @pid_file = case value when Servolux::PidFile value when String path = File.dirname(value) fn = File.basename(value, ".pid") Servolux::PidFile.new(:name => fn, :path => path, :logger => logger) else raise ArgumentError, "#{value.inspect} cannot be used as a PID file" end end
def retrieve_pid
def retrieve_pid @piper ? @piper.pid : pid_file.pid end
def run_startup_command
def run_startup_command after_fork.call if after_fork.respond_to? :call case startup_command when String; exec(startup_command) when Array; exec(*startup_command) when Proc, Method; startup_command.call when ::Servolux::Server; startup_command.startup else raise Error, "Unrecognized startup command #{startup_command.inspect}" end rescue Exception => err unless err.is_a?(SystemExit) logger.fatal err @piper.puts err end ensure @piper.close end
def shutdown
-
(Daemon)
- self
def shutdown return unless alive? case shutdown_command when nil; kill when Integer; kill(shutdown_command) when String; exec(shutdown_command) when Array; exec(*shutdown_command) when Proc, Method; shutdown_command.call when ::Servolux::Server; shutdown_command.shutdown else raise Error, "Unrecognized shutdown command #{shutdown_command.inspect}" end wait_for_shutdown end
def started?
def started? return false unless alive? return true if @logfile_reader.nil? @logfile_reader.updated? end
def startup( do_exit = true )
-
(Daemon)
- self
def startup( do_exit = true ) raise Error, "Fork is not supported in this Ruby environment." unless ::Servolux.fork? return if alive? logger.debug "About to fork ..." @piper = ::Servolux::Piper.daemon(nochdir, noclose) # Make sure we have an idea of the state of the log file BEFORE the child # gets a chance to write to it. @logfile_reader.updated? if @logfile_reader @piper.parent { @piper.timeout = 0.1 wait_for_startup exit!(0) if do_exit } @piper.child { run_startup_command } self end
def startup_command=( val )
-
val
(String, Array
) -- The startup, Proc, Method, Servolux::Server
def startup_command=( val ) @startup_command = val return unless val.is_a?(::Servolux::Server) self.name = val.name self.logger = val.logger self.pid_file = val.pid_file @shutdown_command = nil end
def wait_for
def wait_for start = Time.now nap_time = 0.2 loop do sleep nap_time diff = Time.now - start nap_time = 2*nap_time nap_time = 0.2 if nap_time > 1.6 break true if yield break false if diff >= timeout end end
def wait_for_shutdown
def wait_for_shutdown logger.debug "Waiting for #{name.inspect} to shutdown." return self if wait_for { !alive? } raise Timeout, "#{name.inspect} failed to shutdown in a timely fashion. " \ "The timeout is set at #{timeout} seconds." end
def wait_for_startup
def wait_for_startup logger.debug "Waiting for #{name.inspect} to startup." started = wait_for { rv = started? err = @piper.gets raise StartupError, "Child raised error: #{err.inspect}", err.backtrace unless err.nil? rv } raise Timeout, "#{name.inspect} failed to startup in a timely fashion. " \ "The timeout is set at #{timeout} seconds." unless started logger.info 'Server has daemonized.' ensure @piper.close end