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?

Returns:
  • (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 = {} )

Other tags:
    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' )

Returns:
  • (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 )

Parameters:
  • 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 )

Parameters:
  • 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 )

Raises an ArgumentError if the `value` cannot be used as a PID file.

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

Returns:
  • (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 )

Returns:
  • (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 )

Parameters:
  • val (String, Array, Proc, Method, Servolux::Server) -- The startup
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