class Puma::Server

The HTTP Server itself. Serves out a single Rack app.

This class is used by the `Puma::Single` and `Puma::Cluster` classes to generate one or more `Puma::Server` instances capable of handling requests. Each Puma process will contain one `Puma::Server` instacne.

The `Puma::Server` instance pulls requests from the socket, adds them to a `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.

Each `Puma::Server` will have one reactor and one thread pool.

Constants

UNPACK_TCP_STATE_FROM_TCP_INFO

Attributes

app[RW]
auto_trim_time[RW]
binder[RW]
early_hints[RW]
events[R]
first_data_timeout[RW]
leak_stack_on_error[RW]
max_threads[RW]
min_threads[RW]
persistent_timeout[RW]
reaping_time[RW]
thread[R]

Public Class Methods

new(app, events=Events.stdio, options={}) click to toggle source

Create a server for the rack app app.

events is an object which will be called when certain error events occur to be handled. See Puma::Events for the list of current methods to implement.

Server#run returns a thread that you can join on to wait for the server to do its work.

# File lib/puma/server.rb, line 61
def initialize(app, events=Events.stdio, options={})
  @app = app
  @events = events

  @check, @notify = Puma::Util.pipe

  @status = :stop

  @min_threads = 0
  @max_threads = 16
  @auto_trim_time = 30
  @reaping_time = 1

  @thread = nil
  @thread_pool = nil
  @early_hints = nil

  @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
  @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)

  @binder = Binder.new(events)
  @own_binder = true

  @leak_stack_on_error = true

  @options = options
  @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]

  ENV['RACK_ENV'] ||= "development"

  @mode = :http

  @precheck_closing = true
end

Public Instance Methods

backlog() click to toggle source
# File lib/puma/server.rb, line 165
def backlog
  @thread_pool and @thread_pool.backlog
end
closed_socket?(socket) click to toggle source
# File lib/puma/server.rb, line 137
def closed_socket?(socket)
  return false unless socket.kind_of? TCPSocket
  return false unless @precheck_closing

  begin
    tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
  rescue IOError, SystemCallError
    Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
    @precheck_closing = false
    false
  else
    state = tcp_info.unpack(UNPACK_TCP_STATE_FROM_TCP_INFO)[0]
    # TIME_WAIT: 6, CLOSE: 7, CLOSE_WAIT: 8, LAST_ACK: 9, CLOSING: 11
    (state >= 6 && state <= 9) || state == 11
  end
end
cork_socket(socket) click to toggle source

6 == Socket::IPPROTO_TCP 3 == TCP_CORK 1/0 == turn on/off

# File lib/puma/server.rb, line 121
def cork_socket(socket)
  begin
    socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
  rescue IOError, SystemCallError
    Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
  end
end
handle_servers() click to toggle source
# File lib/puma/server.rb, line 367
def handle_servers
  begin
    check = @check
    sockets = [check] + @binder.ios
    pool = @thread_pool
    queue_requests = @queue_requests

    remote_addr_value = nil
    remote_addr_header = nil

    case @options[:remote_address]
    when :value
      remote_addr_value = @options[:remote_address_value]
    when :header
      remote_addr_header = @options[:remote_address_header]
    end

    while @status == :run
      begin
        ios = IO.select sockets
        ios.first.each do |sock|
          if sock == check
            break if handle_check
          else
            begin
              if io = sock.accept_nonblock
                client = Client.new io, @binder.env(sock)
                if remote_addr_value
                  client.peerip = remote_addr_value
                elsif remote_addr_header
                  client.remote_addr_header = remote_addr_header
                end

                pool << client
                pool.wait_until_not_full
              end
            rescue SystemCallError
              # nothing
            rescue Errno::ECONNABORTED
              # client closed the socket even before accept
              begin
                io.close
              rescue
                Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
              end
            end
          end
        end
      rescue Object => e
        @events.unknown_error self, e, "Listen loop"
      end
    end

    @events.fire :state, @status

    graceful_shutdown if @status == :stop || @status == :restart
    if queue_requests
      @reactor.clear!
      @reactor.shutdown
    end
  rescue Exception => e
    STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
    STDERR.puts e.backtrace
  ensure
    @check.close
    @notify.close

    if @status != :restart and @own_binder
      @binder.close
    end
  end

  @events.fire :state, :done
end
handle_servers_lopez_mode() click to toggle source
# File lib/puma/server.rb, line 224
def handle_servers_lopez_mode
  begin
    check = @check
    sockets = [check] + @binder.ios
    pool = @thread_pool

    while @status == :run
      begin
        ios = IO.select sockets
        ios.first.each do |sock|
          if sock == check
            break if handle_check
          else
            begin
              if io = sock.accept_nonblock
                client = Client.new io, nil
                pool << client
              end
            rescue SystemCallError
              # nothing
            rescue Errno::ECONNABORTED
              # client closed the socket even before accept
              begin
                io.close
              rescue
                Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
              end
            end
          end
        end
      rescue Object => e
        @events.unknown_error self, e, "Listen loop"
      end
    end

    @events.fire :state, @status

    graceful_shutdown if @status == :stop || @status == :restart

  rescue Exception => e
    STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
    STDERR.puts e.backtrace
  ensure
    begin
      @check.close
    rescue
      Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
    end

    @notify.close

    if @status != :restart and @own_binder
      @binder.close
    end
  end

  @events.fire :state, :done
end
inherit_binder(bind) click to toggle source
# File lib/puma/server.rb, line 103
def inherit_binder(bind)
  @binder = bind
  @own_binder = false
end
pool_capacity() click to toggle source

This number represents the number of requests that the server is capable of taking right now.

For example if the number is 5 then it means there are 5 threads sitting idle ready to take a request. If one request comes in, then the value would be 4 until it finishes processing.

# File lib/puma/server.rb, line 181
def pool_capacity
  @thread_pool and @thread_pool.pool_capacity
end
run(background=true) click to toggle source

Runs the server.

If background is true (the default) then a thread is spun up in the background to handle requests. Otherwise requests are handled synchronously.

# File lib/puma/server.rb, line 288
def run(background=true)
  BasicSocket.do_not_reverse_lookup = true

  @events.fire :state, :booting

  @status = :run

  if @mode == :tcp
    return run_lopez_mode(background)
  end

  queue_requests = @queue_requests

  @thread_pool = ThreadPool.new(@min_threads,
                                @max_threads,
                                IOBuffer) do |client, buffer|

    # Advertise this server into the thread
    Thread.current[ThreadLocalKey] = self

    process_now = false

    begin
      if queue_requests
        process_now = client.eagerly_finish
      else
        client.finish
        process_now = true
      end
    rescue MiniSSL::SSLError => e
      ssl_socket = client.io
      addr = ssl_socket.peeraddr.last
      cert = ssl_socket.peercert

      client.close

      @events.ssl_error self, addr, cert, e
    rescue HttpParserError => e
      client.write_400
      client.close

      @events.parse_error self, client.env, e
    rescue ConnectionError, EOFError
      client.close
    else
      if process_now
        process_client client, buffer
      else
        client.set_timeout @first_data_timeout
        @reactor.add client
      end
    end
  end

  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]

  if queue_requests
    @reactor = Reactor.new self, @thread_pool
    @reactor.run_in_thread
  end

  if @reaping_time
    @thread_pool.auto_reap!(@reaping_time)
  end

  if @auto_trim_time
    @thread_pool.auto_trim!(@auto_trim_time)
  end

  @events.fire :state, :running

  if background
    @thread = Thread.new { handle_servers }
    return @thread
  else
    handle_servers
  end
end
run_lopez_mode(background=true) click to toggle source

Lopez Mode == raw tcp apps

# File lib/puma/server.rb, line 187
def run_lopez_mode(background=true)
  @thread_pool = ThreadPool.new(@min_threads,
                                @max_threads,
                                Hash) do |client, tl|

    io = client.to_io
    addr = io.peeraddr.last

    if addr.empty?
      # Set unix socket addrs to localhost
      addr = "127.0.0.1:0"
    else
      addr = "#{addr}:#{io.peeraddr[1]}"
    end

    env = { 'thread' => tl, REMOTE_ADDR => addr }

    begin
      @app.call env, client.to_io
    rescue Object => e
      STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
      STDERR.puts e.backtrace
    end

    client.close unless env['detach']
  end

  @events.fire :state, :running

  if background
    @thread = Thread.new { handle_servers_lopez_mode }
    return @thread
  else
    handle_servers_lopez_mode
  end
end
running() click to toggle source
# File lib/puma/server.rb, line 169
def running
  @thread_pool and @thread_pool.spawned
end
tcp_mode!() click to toggle source
# File lib/puma/server.rb, line 108
def tcp_mode!
  @mode = :tcp
end
uncork_socket(socket) click to toggle source
# File lib/puma/server.rb, line 129
def uncork_socket(socket)
  begin
    socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
  rescue IOError, SystemCallError
    Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
  end
end