class Bluepill::Application

Constants

PROCESS_COMMANDS

Attributes

base_dir[RW]
groups[RW]
kill_timeout[RW]
log_file[RW]
logger[RW]
name[RW]
pid_file[RW]
pids_dir[RW]
socket[RW]
work_queue[RW]

Public Class Methods

new(name, options = {}) click to toggle source
# File lib/bluepill/application.rb, line 13
def initialize(name, options = {})
  self.name = name

  @foreground   = options[:foreground]
  self.log_file = options[:log_file]
  self.base_dir = ProcessJournal.base_dir = options[:base_dir] ||
    ENV['BLUEPILL_BASE_DIR'] ||
    (::Process.euid != 0 ? File.join(ENV['HOME'], '.bluepill') : '/var/run/bluepill')
  self.pid_file = File.join(base_dir, 'pids', self.name + '.pid')
  self.pids_dir = File.join(base_dir, 'pids', self.name)
  self.kill_timeout = options[:kill_timeout] || 10

  self.groups = {}

  self.logger = ProcessJournal.logger = Bluepill::Logger.new(log_file: log_file, stdout: foreground?).prefix_with(self.name)

  setup_signal_traps
  setup_pids_dir

  @mutex = Mutex.new
end

Public Instance Methods

add_process(process, group_name = nil) click to toggle source
# File lib/bluepill/application.rb, line 60
def add_process(process, group_name = nil)
  group_name = group_name.to_s if group_name

  groups[group_name] ||= Group.new(group_name, logger: logger.prefix_with(group_name))
  groups[group_name].add_process(process)
end
foreground?() click to toggle source
# File lib/bluepill/application.rb, line 35
def foreground?
  !!@foreground
end
load() click to toggle source
# File lib/bluepill/application.rb, line 43
def load
  start_server
rescue StandardError => e
  $stderr.puts('Failed to start bluepill:')
  $stderr.puts(format('%s `%s`', e.class.name, e.message))
  $stderr.puts(e.backtrace)
  exit(5)
end
mutex(&b) click to toggle source
# File lib/bluepill/application.rb, line 39
def mutex(&b)
  @mutex.synchronize(&b)
end
version() click to toggle source
# File lib/bluepill/application.rb, line 67
def version
  Bluepill::Version
end

Protected Instance Methods

cleanup() click to toggle source
# File lib/bluepill/application.rb, line 155
def cleanup
  ProcessJournal.kill_all_from_all_journals
  ProcessJournal.clear_all_atomic_fs_locks
  begin
    System.delete_if_exists(socket.path) if socket
  rescue IOError
  end
  System.delete_if_exists(pid_file)
end
kill_previous_bluepill() click to toggle source
# File lib/bluepill/application.rb, line 187
def kill_previous_bluepill
  return unless File.exist?(pid_file)
  previous_pid = File.read(pid_file).to_i
  return unless System.pid_alive?(previous_pid)
  ::Process.kill(0, previous_pid)
  puts "Killing previous bluepilld[#{previous_pid}]"
  ::Process.kill(2, previous_pid)
rescue => e
  $stderr.puts 'Encountered error trying to kill previous bluepill:'
  $stderr.puts "#{e.class}: #{e.message}"
  exit(4) unless e.is_a?(Errno::ESRCH)
else
  kill_timeout.times do |_i|
    sleep 0.5
    break unless System.pid_alive?(previous_pid)
  end

  if System.pid_alive?(previous_pid)
    $stderr.puts "Previous bluepilld[#{previous_pid}] didn't die"
    exit(4)
  end
end
run() click to toggle source
# File lib/bluepill/application.rb, line 144
def run
  @running = true # set to false by signal trap
  while @running
    mutex do
      System.reset_data
      groups.each { |_, group| group.tick }
    end
    sleep 1
  end
end
send_to_process_or_group(method, group_name, process_name) click to toggle source
# File lib/bluepill/application.rb, line 73
def send_to_process_or_group(method, group_name, process_name)
  if group_name.nil? && process_name.nil?
    groups.values.collect do |group|
      group.send(method)
    end.flatten
  elsif groups.key?(group_name)
    groups[group_name].send(method, process_name)
  elsif process_name.nil?
    # they must be targeting just by process name
    process_name = group_name
    groups.values.collect do |group|
      group.send(method, process_name)
    end.flatten
  else
    []
  end
end
setup_pids_dir() click to toggle source
# File lib/bluepill/application.rb, line 180
def setup_pids_dir
  FileUtils.mkdir_p(pids_dir) unless File.exist?(pids_dir)
  # we need everybody to be able to write to the pids_dir as processes managed by
  # bluepill will be writing to this dir after they've dropped privileges
  FileUtils.chmod(0777, pids_dir)
end
setup_signal_traps() click to toggle source
# File lib/bluepill/application.rb, line 165
def setup_signal_traps
  terminator = proc do
    puts 'Terminating...'
    cleanup
    @running = false
  end

  Signal.trap('TERM', &terminator)
  Signal.trap('INT', &terminator)

  Signal.trap('HUP') do
    logger.reopen if logger
  end
end
start_listener() click to toggle source
# File lib/bluepill/application.rb, line 91
def start_listener
  @listener_thread.kill if @listener_thread
  @listener_thread = Thread.new do
    loop do
      begin
        client = socket.accept
        client.close_on_exec = true  if client.respond_to?(:close_on_exec=)
        command, *args = client.readline.strip.split(':')
        response = begin
          mutex { send(command, *args) }
        rescue => e
          e
        end
        client.write(Marshal.dump(response))
      rescue StandardError => e
        logger.err(format('Got exception in cmd listener: %s `%s`', e.class.name, e.message))
        e.backtrace.each { |l| logger.err(l) }
      ensure
        begin
          client.close
        rescue IOError
          # closed stream
        end
      end
    end
  end
end
start_server() click to toggle source
# File lib/bluepill/application.rb, line 119
def start_server
  kill_previous_bluepill
  ProcessJournal.kill_all_from_all_journals
  ProcessJournal.clear_all_atomic_fs_locks

  begin
    ::Process.setpgid(0, 0)
  rescue Errno::EPERM
  end

  Daemonize.daemonize unless foreground?

  logger.reopen

  $0 = "bluepilld: #{name}"

  groups.each { |_, group| group.determine_initial_state }

  write_pid_file
  self.socket = Bluepill::Socket.server(base_dir, name)
  start_listener

  run
end
write_pid_file() click to toggle source
# File lib/bluepill/application.rb, line 210
def write_pid_file
  File.open(pid_file, 'w') { |x| x.write(::Process.pid) }
end