class FastlyNsq::CLI

Provides functionality to support running FastlyNsq as a command line application that listens and processes NSQ messages.

@example

begin
  cli = FastlyNsq::CLI.instance
  cli.parse_options
  cli.run
rescue => e
  raise e if $DEBUG
  STDERR.puts e.message
  STDERR.puts e.backtrace.join("\n")
  exit 1
end

Attributes

options[R]

Public Instance Methods

parse_options(args = ARGV) click to toggle source
# File lib/fastly_nsq/cli.rb, line 32
def parse_options(args = ARGV)
  parse(args)
  setup_logger
  check_pid
  daemonize if daemonize?
  write_pid
end
run() click to toggle source
# File lib/fastly_nsq/cli.rb, line 40
def run
  startup

  launcher.beat

  read_loop
rescue Interrupt
  FastlyNsq.logger.info 'Shutting down'
  launcher.stop
  # Explicitly exit so busy Processor threads can't block
  # process shutdown.
  FastlyNsq.logger.info 'Bye!'
  exit(0)
end

Private Instance Methods

boot_rails() click to toggle source
# File lib/fastly_nsq/cli.rb, line 78
def boot_rails
  return unless ENV['RAILS_ENV']

  require 'rails'
  require File.expand_path('./config/application.rb')
  require File.expand_path('./config/environment.rb')
end
check_pid() click to toggle source
# File lib/fastly_nsq/cli.rb, line 125
def check_pid
  if pidfile?
    case pid_status(pidfile)
    when :running, :not_owned
      puts "A server is already running. Check #{pidfile}"
      exit(1)
    when :dead
      File.delete(pidfile)
    end
  end
end
daemonize() click to toggle source
# File lib/fastly_nsq/cli.rb, line 214
def daemonize
  return unless options[:daemonize]

  files_to_reopen = []
  ObjectSpace.each_object(File) do |file|
    files_to_reopen << file unless file.closed?
  end

  ::Process.daemon(true, true)

  reopen(files_to_reopen)

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

  setup_logger
end
daemonize?() click to toggle source
# File lib/fastly_nsq/cli.rb, line 258
def daemonize?
  options[:daemonize]
end
handle_signal(sig) click to toggle source
# File lib/fastly_nsq/cli.rb, line 185
def handle_signal(sig)
  FastlyNsq.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'
    FastlyNsq.logger.info 'Received USR1, no longer accepting new work'
    launcher.stop_listeners
  when 'TTIN'
    handle_ttin
  end
end
handle_ttin() click to toggle source
# File lib/fastly_nsq/cli.rb, line 203
def handle_ttin
  Thread.list.each do |thread|
    FastlyNsq.logger.warn "Thread TID-#{thread.object_id.to_s(36)} #{thread['fastly_nsq_label']}"
    if thread.backtrace
      FastlyNsq.logger.warn thread.backtrace.join("\n")
    else
      FastlyNsq.logger.warn '<no backtrace available>'
    end
  end
end
launcher() click to toggle source
# File lib/fastly_nsq/cli.rb, line 57
def launcher
  @launcher ||= FastlyNsq::Launcher.new(options)
end
logfile() click to toggle source
# File lib/fastly_nsq/cli.rb, line 262
def logfile
  options[:logfile]
end
logfile?() click to toggle source
# File lib/fastly_nsq/cli.rb, line 250
def logfile?
  !logfile.nil?
end
max_threads() click to toggle source
# File lib/fastly_nsq/cli.rb, line 266
def max_threads
  options[:max_threads]
end
parse(args) click to toggle source
# File lib/fastly_nsq/cli.rb, line 86
def parse(args)
  opts = {}

  @parser = OptionParser.new do |o|
    o.on '-c', '--concurrency COUNT', 'Number of threads used to process messages' do |arg|
      opts[:max_threads] = arg
    end

    o.on '-d', '--daemon', 'Daemonize process' do |arg|
      opts[:daemonize] = 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 '-r', '--require [PATH|DIR]', 'Location of message_processor definition' do |arg|
      opts[:require] = arg
    end

    o.on '-v', '--verbose', 'enable verbose logging output' do |arg|
      opts[:verbose] = arg
    end

    o.on '-t', '--timeout SECONDS', 'shutdown deadline timeout' do |arg|
      opts[:timeout] = arg
    end
  end

  @parser.banner = 'fastly_nsq [options]'
  @parser.parse!(args)

  @options = opts
end
pid_status(pidfile) click to toggle source
# File lib/fastly_nsq/cli.rb, line 137
def pid_status(pidfile)
  return :exited unless File.exist?(pidfile)
  pid = ::File.read(pidfile).to_i
  return :dead if pid.zero?
  Process.kill(0, pid) # check process status
  :running
rescue Errno::ESRCH
  :dead
rescue Errno::EPERM
  :not_owned
end
pidfile() click to toggle source
# File lib/fastly_nsq/cli.rb, line 270
def pidfile
  options[:pidfile]
end
pidfile?() click to toggle source
# File lib/fastly_nsq/cli.rb, line 254
def pidfile?
  !pidfile.nil?
end
read_loop() click to toggle source
# File lib/fastly_nsq/cli.rb, line 61
def read_loop
  trapped_read_io = trap_signals
  loop do
    readable_io = IO.select([trapped_read_io])
    break unless readable_io
    signal = readable_io.first[0].gets.strip
    handle_signal signal
  end
end
reopen(files) click to toggle source
# File lib/fastly_nsq/cli.rb, line 237
def reopen(files)
  files.each do |file|
    begin
      file.reopen file.path, 'a+'
      file.sync = true
    rescue IOError => e
      FastlyNsq.logger.warn "IOError reopening file:  #{e.message}"
    rescue StandardError => e
      FastlyNsq.logger.error "Non IOError reopening file:  #{e.message}"
    end
  end
end
setup_logger() click to toggle source
# File lib/fastly_nsq/cli.rb, line 149
def setup_logger
  FastlyNsq.logger = Logger.new(options[:logfile]) if options[:logfile]

  FastlyNsq.logger.level = ::Logger::DEBUG if options[:verbose]
end
startup() click to toggle source
# File lib/fastly_nsq/cli.rb, line 71
def startup
  boot_rails
  require options[:require] if options[:require]
  FastlyNsq.logger.info "Running in #{RUBY_DESCRIPTION}"
  FastlyNsq.logger.info 'Starting processing, hit Ctrl-C to stop' unless options[:daemon]
end
trap_signals() click to toggle source
# File lib/fastly_nsq/cli.rb, line 169
def trap_signals
  self_read, self_write = IO.pipe
  sigs = %w[INT TERM TTIN USR1]

  sigs.each do |sig|
    begin
      trap sig do
        self_write.puts(sig)
      end
    rescue ArgumentError
      puts "Signal #{sig} not supported"
    end
  end
  self_read
end
write_pid() click to toggle source
# File lib/fastly_nsq/cli.rb, line 155
def write_pid
  if pidfile?
    begin
      File.open(pidfile, ::File::CREAT | ::File::EXCL | ::File::WRONLY) do |f|
        f.write Process.pid.to_s
      end
      at_exit { File.delete(pidfile) if File.exist?(pidfile) }
    rescue Errno::EEXIST
      check_pid
      retry
    end
  end
end