class Puma::Server - puma-3.10.0 Documentation

# class Puma::Server

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

### Constants

UNPACK_TCP_STATE_FROM_TCP_INFO

### Attributes

app[RW]
auto_trim_time[RW]
binder[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.

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

    # File lib/puma/server.rb, line 50
    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

      @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 153
    def backlog
      @thread_pool and @thread_pool.backlog
    end

closed_socket?(socket) click to toggle source

    # File lib/puma/server.rb, line 125
    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 109
    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 334
    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
                  io.close rescue nil
                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 200
    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
                  io.close rescue nil
                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
        @check.close
        @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 91
    def inherit_binder(bind)
      @binder = bind
      @own_binder = false
    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 255
    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
          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 163
    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 157
    def running
      @thread_pool and @thread_pool.spawned
    end

tcp_mode!() click to toggle source

    # File lib/puma/server.rb, line 96
    def tcp_mode!
      @mode = :tcp
    end

uncork_socket(socket) click to toggle source

    # File lib/puma/server.rb, line 117
    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