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
Public Class Methods
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
# File lib/puma/server.rb, line 165 def backlog @thread_pool and @thread_pool.backlog end
# 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
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
# 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
# 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
# File lib/puma/server.rb, line 103 def inherit_binder(bind) @binder = bind @own_binder = false end
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
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
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
# File lib/puma/server.rb, line 169 def running @thread_pool and @thread_pool.spawned end
# File lib/puma/server.rb, line 108 def tcp_mode! @mode = :tcp end
# 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