class Roundhouse::CLI

Attributes

code[RW]

Used for CLI testing

environment[RW]
launcher[RW]

Public Class Methods

banner() click to toggle source
new() click to toggle source
# File lib/roundhouse/cli.rb, line 31
def initialize
  @code = nil
end

Public Instance Methods

handle_signal(sig) click to toggle source
# File lib/roundhouse/cli.rb, line 116
def handle_signal(sig)
  Roundhouse.logger.debug "Got #{sig} signal"
  case sig
  when 'INT'
    # Handle Ctrl-C in JRuby like MRI
    # http://jira.codehaus.org/browse/JRUBY-4637
    raise Interrupt
  when 'TERM'
    # Heroku sends TERM and then waits 10 seconds for process to exit.
    raise Interrupt
  when 'USR1'
    Roundhouse.logger.info "Received USR1, no longer accepting new work"
    launcher.manager.async.stop
    fire_event(:quiet, true)
  when 'USR2'
    if Roundhouse.options[:logfile]
      Roundhouse.logger.info "Received USR2, reopening log file"
      Roundhouse::Logging.reopen_logs
    end
  when 'TTIN'
    Thread.list.each do |thread|
      Roundhouse.logger.warn "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
      if thread.backtrace
        Roundhouse.logger.warn thread.backtrace.join("\n")
      else
        Roundhouse.logger.warn "<no backtrace available>"
      end
    end
  end
end
parse(args=ARGV) click to toggle source
# File lib/roundhouse/cli.rb, line 35
def parse(args=ARGV)
  @code = nil

  setup_options(args)
  initialize_logger
  validate!
  daemonize
  write_pid
  load_celluloid
end
run() click to toggle source

Code within this method is not tested because it alters global process state irreversibly. PRs which improve the test coverage of Roundhouse::CLI are welcomed.

# File lib/roundhouse/cli.rb, line 49
def run
  boot_system
  print_banner

  self_read, self_write = IO.pipe

  %w(INT TERM USR1 USR2 TTIN).each do |sig|
    begin
      trap sig do
        self_write.puts(sig)
      end
    rescue ArgumentError
      puts "Signal #{sig} not supported"
    end
  end

  logger.info "Running in #{RUBY_DESCRIPTION}"
  logger.info Roundhouse::LICENSE

  fire_event(:startup)

  logger.debug {
    "Middleware: #{Roundhouse.server_middleware.map(&:klass).join(', ')}"
  }

  Roundhouse.redis do |conn|
    # touch the connection pool so it is created before we
    # launch the actors.
  end

  if !options[:daemon]
    logger.info 'Starting processing, hit Ctrl-C to stop'
  end

  require 'roundhouse/launcher'
  @launcher = Roundhouse::Launcher.new(options)

  begin
    launcher.run

    while readable_io = IO.select([self_read])
      signal = readable_io.first[0].gets.strip
      handle_signal(signal)
    end
  rescue Interrupt
    logger.info 'Shutting down'
    launcher.stop
    fire_event(:shutdown, true)
    # Explicitly exit so busy Processor threads can't block
    # process shutdown.
    exit(0)
  end
end

Private Instance Methods

boot_system() click to toggle source
# File lib/roundhouse/cli.rb, line 224
def boot_system
  ENV['RACK_ENV'] = ENV['RAILS_ENV'] = environment

  raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require])

  if File.directory?(options[:require])
    require 'rails'
    if ::Rails::VERSION::MAJOR < 4
      require 'roundhouse/rails'
      require File.expand_path("#{options[:require]}/config/environment.rb")
      ::Rails.application.eager_load!
    else
      # Painful contortions, see 1791 for discussion
      require File.expand_path("#{options[:require]}/config/application.rb")
      ::Rails::Application.initializer "roundhouse.eager_load" do
        ::Rails.application.config.eager_load = true
      end
      require 'roundhouse/rails'
      require File.expand_path("#{options[:require]}/config/environment.rb")
    end
    options[:tag] ||= default_tag
  else
    require options[:require]
  end
end
daemonize() click to toggle source
# File lib/roundhouse/cli.rb, line 171
def daemonize
  return unless options[:daemon]

  raise ArgumentError, "You really should set a logfile if you're going to daemonize" unless options[:logfile]
  files_to_reopen = []
  ObjectSpace.each_object(File) do |file|
    files_to_reopen << file unless file.closed?
  end

  ::Process.daemon(true, true)

  files_to_reopen.each do |file|
    begin
      file.reopen file.path, "a+"
      file.sync = true
    rescue ::Exception
    end
  end

  [$stdout, $stderr].each do |io|
    File.open(options[:logfile], 'ab') do |f|
      io.reopen(f)
    end
    io.sync = true
  end
  $stdin.reopen('/dev/null')

  initialize_logger
end
default_tag() click to toggle source
# File lib/roundhouse/cli.rb, line 250
def default_tag
  dir = ::Rails.root
  name = File.basename(dir)
  if name.to_i != 0 && prevdir = File.dirname(dir) # Capistrano release directory?
    if File.basename(prevdir) == 'releases'
      return File.basename(File.dirname(prevdir))
    end
  end
  name
end
initialize_logger() click to toggle source
# File lib/roundhouse/cli.rb, line 341
def initialize_logger
  Roundhouse::Logging.initialize_logger(options[:logfile]) if options[:logfile]

  Roundhouse.logger.level = ::Logger::DEBUG if options[:verbose]
end
load_celluloid() click to toggle source
# File lib/roundhouse/cli.rb, line 158
def load_celluloid
  raise "Celluloid cannot be required until here, or it will break Roundhouse's daemonization" if defined?(::Celluloid) && options[:daemon]

  # Celluloid can't be loaded until after we've daemonized
  # because it spins up threads and creates locks which get
  # into a very bad state if forked.
  require 'celluloid/current'
  Celluloid.logger = (options[:verbose] ? Roundhouse.logger : nil)

  require 'roundhouse/manager'
  require 'roundhouse/scheduled'
end
options() click to toggle source
# File lib/roundhouse/cli.rb, line 220
def options
  Roundhouse.options
end
parse_config(cfile) click to toggle source
# File lib/roundhouse/cli.rb, line 356
def parse_config(cfile)
  opts = {}
  if File.exist?(cfile)
    opts = YAML.load(ERB.new(IO.read(cfile)).result) || opts
    opts = opts.merge(opts.delete(environment) || {})
  else
    # allow a non-existent config file so Roundhouse
    # can be deployed by cap with just the defaults.
  end
  ns = opts.delete(:namespace)
  if ns
    # logger hasn't been initialized yet, puts is all we have.
    puts("namespace should be set in your ruby initializer, is ignored in config file")
    puts("config.redis = { :url => ..., :namespace => '#{ns}' }")
  end
  opts
end
parse_options(argv) click to toggle source
# File lib/roundhouse/cli.rb, line 277
def parse_options(argv)
  opts = {}

  @parser = OptionParser.new do |o|
    o.on '-c', '--concurrency INT', "processor threads to use" do |arg|
      opts[:concurrency] = Integer(arg)
    end

    o.on '-d', '--daemon', "Daemonize process" do |arg|
      opts[:daemon] = arg
    end

    o.on '-e', '--environment ENV', "Application environment" do |arg|
      opts[:environment] = arg
    end

    o.on '-g', '--tag TAG', "Process tag for procline" do |arg|
      opts[:tag] = arg
    end

    o.on '-i', '--index INT', "unique process index on this machine" do |arg|
      opts[:index] = Integer(arg.match(/\d+/)[0])
    end

    o.on '-r', '--require [PATH|DIR]', "Location of Rails application with workers or file to require" do |arg|
      opts[:require] = arg
    end

    o.on '-t', '--timeout NUM', "Shutdown timeout" do |arg|
      opts[:timeout] = Integer(arg)
    end

    o.on "-v", "--verbose", "Print more verbose output" do |arg|
      opts[:verbose] = arg
    end

    o.on '-C', '--config PATH', "path to YAML config file" do |arg|
      opts[:config_file] = arg
    end

    o.on '-L', '--logfile PATH', "path to writable logfile" do |arg|
      opts[:logfile] = arg
    end

    o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
      opts[:pidfile] = arg
    end

    o.on '-V', '--version', "Print version and exit" do |arg|
      puts "Roundhouse #{Roundhouse::VERSION}"
      die(0)
    end
  end

  @parser.banner = "roundhouse [options]"
  @parser.on_tail "-h", "--help", "Show help" do
    logger.info @parser
    die 1
  end
  @parser.parse!(argv)
  opts[:config_file] ||= 'config/roundhouse.yml' if File.exist?('config/roundhouse.yml')
  opts
end
print_banner() click to toggle source
set_environment(cli_env) click to toggle source
# File lib/roundhouse/cli.rb, line 201
def set_environment(cli_env)
  @environment = cli_env || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
end
setup_options(args) click to toggle source
# File lib/roundhouse/cli.rb, line 208
def setup_options(args)
  opts = parse_options(args)
  set_environment opts[:environment]

  cfile = opts[:config_file]
  opts = parse_config(cfile).merge(opts) if cfile

  opts[:strict] = true if opts[:strict].nil?

  options.merge!(opts)
end
validate!() click to toggle source
# File lib/roundhouse/cli.rb, line 261
def validate!
  if !File.exist?(options[:require]) ||
     (File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
    logger.info "=================================================================="
    logger.info "  Please point roundhouse to a Rails 3/4 application or a Ruby file  "
    logger.info "  to load your worker classes with -r [DIR|FILE]."
    logger.info "=================================================================="
    logger.info @parser
    die(1)
  end

  [:concurrency, :timeout].each do |opt|
    raise ArgumentError, "#{opt}: #{options[opt]} is not a valid value" if options.has_key?(opt) && options[opt].to_i <= 0
  end
end
write_pid() click to toggle source
# File lib/roundhouse/cli.rb, line 347
def write_pid
  if path = options[:pidfile]
    pidfile = File.expand_path(path)
    File.open(pidfile, 'w') do |f|
      f.puts ::Process.pid
    end
  end
end