class Reifier::Server

Public Class Methods

new(app, options = {}) click to toggle source
# File lib/reifier/server.rb, line 3
def initialize(app, options = {})
  @app     = app
  @options = options
end

Public Instance Methods

load_configuration() click to toggle source
# File lib/reifier/server.rb, line 8
def load_configuration
  if defined?(Rails)
    path = Rails.root.join('config/reifier.rb')
  else
    path = Dir.pwd + '/reifier.rb'
  end

  return unless File.exist?(path)

  lines = File.read(path).split("\n")

  lines.each do |line|
    eval(line)
  end

  puts "======= Loaded settings from #{path} =======\n"
rescue NoMethodError => e
  raise UnsupportedOptionError, "Option #{e.name} is not supported from config file"
end
start() click to toggle source
# File lib/reifier/server.rb, line 28
def start
  server = TCPServer.new(@options[:Host], @options[:Port])

  puts "# Ruby version: #{RUBY_VERSION}"
  puts "# Min threads: #{@options[:MinThreads]}, max threads: #{@options[:MaxThreads]}"
  puts "# Environment: #{@options[:environment]}"
  puts "# Master PID: #{Process.pid}"
  puts "# Listening on tcp://#{server.addr.last}:#{@options[:Port]}"

  if @options[:Workers]
    start_clustered(server)
  else
    start_single(server)
  end
end

Private Instance Methods

start!(server) click to toggle source
# File lib/reifier/server.rb, line 98
def start!(server)
  pool = Concurrent::ThreadPoolExecutor.new(
    min_threads:     @options[:MinThreads],
    max_threads:     @options[:MaxThreads],
    max_queue:       0,
    fallback_policy: :caller_runs,
  )

  loop do
    socket = server.accept
    socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)

    Concurrent::Future.new(executor: pool) do
      begin
        request  = Request.new(socket, @options)
        response = Response.new(socket)

        request.handle

        response.protocol = request.protocol
        response << @app.call(request.rack_env)

        response.handle
      rescue EOFError
        # nothing, shit happens
      rescue Exception => e
        socket.close

        STDERR.puts ERROR_HEADER
        STDERR.puts "#{e.class}: #{e}"
        STDERR.puts e.backtrace
        STDERR.puts ERROR_FOOTER
      end
    end.execute
  end
end
start_clustered(server) click to toggle source
# File lib/reifier/server.rb, line 51
def start_clustered(server)
  puts '# Started in clustered mode'
  child_pids = []

  puts '# ================================'
  puts "# Spinning up #{@options[:Workers].to_i} Workers"
  @options[:Workers].to_i.times do |i|
    pid = fork do
      start!(server)
    end

    puts "# Worker #{i} started with pid: #{pid}"

    child_pids << pid
  end

  Signal.trap 'SIGINT' do
    puts "\n======= Cleaning up #{child_pids.length} Workers =======\n"
    child_pids.each do |cpid|
      begin
        Process.kill(:INT, cpid)
      rescue Errno::ESRCH
      end
    end

    exit
  end

  loop do
    pid = Process.wait
    STDERR.puts "Process #{pid} crashed"

    child_pids.delete(pid)
    child_pids << spawn_worker(server)
  end
end
start_single(server) click to toggle source
# File lib/reifier/server.rb, line 46
def start_single(server)
  puts '# Started in single mode'
  start!(server)
end
threads(min, max) click to toggle source
# File lib/reifier/server.rb, line 89
def threads(min, max)
  @options[:MinThreads] = min
  @options[:MaxThreads] = max
end
workers(count) click to toggle source
# File lib/reifier/server.rb, line 94
def workers(count)
  @options[:Workers] = count
end