class Pidly::Control

Pidly daemon control

Attributes

allow_multiple[RW]
daemon[RW]
error_count[RW]
log_file[RW]
messages[RW]
name[RW]
path[RW]
pid[RW]
pid_file[RW]
sync_log[RW]
timeout[RW]
verbose[RW]

Public Class Methods

new(options={}) click to toggle source

Initialize control object

@param [Hash] options The options to create a controller with.

@option options [String] :name Daemon name

@option options [String] :path Path to create the log/pids directory

@option options [String] :pid_file Pid file path

@option options [String] :log_file Log file path

@option options [true, false] :sync_log Synchronize log files

@option options [true, false] :allow_multiple

Allow multiple daemons of the same type

@option options [true, false] :sync_log Synchronize log files

@option options [String] :signal Trap signal

@option options [Integer] :timeout Timeout for Process#wait

@option options [true, false] :verbose Display daemon messages

@option options [true, false] :logger Enable daemon logging

@return [Control] Control object

@raise [RuntimeError]

Raise exception if path does not exist

@raise [RuntimeError]

Raise exception if path is not readable or writable.
# File lib/pidly/control.rb, line 63
def initialize(options={})

  @messages = []

  @error_count = 0

  @name = options.fetch(:name)

  @path = if options.has_key?(:path)
    Pathname.new(options.fetch(:path))
  else
    Pathname.new('/tmp')
  end

  unless @path.directory?
    raise('Path does not exist or is not a directory.')
  end

  unless @path.readable? && @path.writable?
    raise('Path must be readable and writable.')
  end

  @pid_file = if options.has_key?(:pid_file)
    Pathname.new(options.fetch(:pid_file))
  else
    Pathname.new(File.join(@path.to_s, 'pids', @name + '.pid'))
  end

  @log_file = if options.has_key?(:log_file)
    Pathname.new(options.fetch(:log_file))
  else
    Pathname.new(File.join(@path.to_s, 'logs', @name + '.log'))
  end

  @pid = fetch_pid

  @sync_log = options.fetch(:sync_log, true)

  @allow_multiple = options.fetch(:allow_multiple, false)

  @signal = options.fetch(:signal, "TERM")

  @timeout = options.fetch(:timeout, 10)

  @verbosity = options.fetch(:verbose, true)

  @logger = options.fetch(:logger, true)

  validate_callbacks!

  validate_files_and_paths!
end
spawn(options={}) click to toggle source

Spawn

@param [Hash] options Control object options

@return [Control] Control object

@see initialize

@example

Test.spawn(
  :path => '/tmp',
  :verbose => true
)
# File lib/pidly/control.rb, line 131
def self.spawn(options={})
  @daemon = new(options)
end

Public Instance Methods

clean!() click to toggle source

Clean

Remove all files created by the daemon.

# File lib/pidly/control.rb, line 293
def clean!
  FileUtils.rm(@log_file)
  FileUtils.rm(@pid_file)
rescue Errno::ENOENT
end
fetch_class_var(name) click to toggle source

Fetch Class Variable

@param [Symbol] name Callback name

@return [Symbol, Proc, nil]

Returns either a method or block of code to be executed when
the callback is called. If no value is accossiated with the
call variable `nil` will be returned to the caller.
# File lib/pidly/control.rb, line 309
def fetch_class_var(name)
  Control.instance_eval do
    return nil unless class_variable_defined?(:"@@#{name}")

    class_variable_get(:"@@#{name}")
  end
end
kill(remove_pid_file=true) click to toggle source

Kill

@param [String] remove_pid_file Remove the daemon pid file

# File lib/pidly/control.rb, line 261
def kill(remove_pid_file=true)
  if running?
    log(:info, "Killing \"#{@name}\" (PID: #{@pid})")
    execute_callback(:kill)
    Process.kill 9, @pid
  end

  FileUtils.rm(@pid_file) if remove_pid_file
rescue Errno::ENOENT
end
restart() click to toggle source

Restart

Restart the daemon

# File lib/pidly/control.rb, line 252
def restart
  stop; sleep 1 while running?; start
end
running?() click to toggle source

Running?

@return [true, false] Return the running status of the daemon.

# File lib/pidly/control.rb, line 277
def running?
  Process.kill 0, @pid
  true
rescue Errno::ESRCH
  false
rescue Errno::EPERM
  true
rescue
  false
end
start() click to toggle source

Start

Validate callbacks and start daemon

# File lib/pidly/control.rb, line 140
def start

  unless @allow_multiple
    if running?
      log(:error, "\"#{@name}\" is already running (PID: #{@pid})")
      return
    end
  end

  @pid = fork do
    begin
      Process.setsid

      open(@pid_file, 'w') do |f|
        f << Process.pid
        @pid = Process.pid
      end

      execute_callback(:before_start)

      Dir.chdir @path.to_s
      File.umask 0000

      if @logger
        log = File.new(@log_file, "a")
        log.sync = @sync_log

        STDIN.reopen "/dev/null"
        STDOUT.reopen log
        STDERR.reopen STDOUT
      end

      trap("TERM") do
        stop
      end

      execute_callback(:start)

    rescue RuntimeError => message
      STDERR.puts message
      STDERR.puts message.backtrace

      execute_callback(:error)
    rescue => message
      STDERR.puts message
      STDERR.puts message.backtrace

      execute_callback(:error)
    end
  end

rescue => message
  STDERR.puts message
  STDERR.puts message.backtrace
  execute_callback(:error)
end
status() click to toggle source

Status

Return current daemon status and pid

@return [String] Status

# File lib/pidly/control.rb, line 239
def status
  if running?
    log(:info, "\"#{@name}\" is running (PID: #{@pid})")
  else
    log(:info, "\"#{@name}\" is NOT running")
  end
end
stop() click to toggle source

Stop

Stop daemon and remove pid file

# File lib/pidly/control.rb, line 202
def stop

  if running?

    Process.kill(@signal, @pid)
    FileUtils.rm(@pid_file)

    execute_callback(:stop)

    begin
      Process.wait(@pid)
    rescue Errno::ECHILD
    end

    @timeout.downto(0) do
      sleep 1
      exit unless running?
    end

    Process.kill 9, @pid if running?
    execute_callback(:after_stop)

  else
    FileUtils.rm(@pid_file) if File.exists?(@pid_file)
    log(:info, "\"#{@name}\" PID file not found.")
  end

rescue Errno::ENOENT
end

Private Instance Methods

execute_callback(callback_name) click to toggle source
# File lib/pidly/control.rb, line 346
def execute_callback(callback_name)
  @error_count += 1 if callback_name == :error

  if (callback = fetch_class_var(callback_name))

    if callback.kind_of?(Symbol)
      self.send(callback.to_sym)
    elsif callback.respond_to?(:call)
      self.instance_eval(&callback)
    else
      nil
    end

  end
end
fetch_pid() click to toggle source
# File lib/pidly/control.rb, line 362
def fetch_pid
  IO.read(@pid_file).to_i
rescue
  nil
end
validate_callbacks!() click to toggle source
# File lib/pidly/control.rb, line 327
def validate_callbacks!
  parent = self
  Control.instance_eval do

    unless class_variable_defined?(:"@@start")
      raise('You must define a "start" callback.')
    end

    class_variables.each do |cvar|
      value = class_variable_get(:"#{cvar}")

      next unless value.is_a?(Symbol)
      next if parent.respond_to?(value)

      raise("Undefined callback method: #{value}")
    end
  end
end
validate_files_and_paths!() click to toggle source
# File lib/pidly/control.rb, line 317
def validate_files_and_paths!
  unless @log_file.directory?
    FileUtils.mkdir_p(@log_file.dirname)
  end

  unless @pid_file.directory?
    FileUtils.mkdir_p(@pid_file.dirname)
  end
end