puma 5.0.0-java → 5.1.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +1190 -574
- data/README.md +28 -20
- data/bin/puma-wild +3 -9
- data/docs/compile_options.md +19 -0
- data/docs/deployment.md +5 -6
- data/docs/fork_worker.md +2 -0
- data/docs/jungle/README.md +0 -4
- data/docs/jungle/rc.d/puma +2 -2
- data/docs/nginx.md +1 -1
- data/docs/restart.md +46 -23
- data/docs/systemd.md +25 -3
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +4 -5
- data/ext/puma_http11/http11_parser.c +64 -64
- data/ext/puma_http11/mini_ssl.c +39 -37
- data/ext/puma_http11/puma_http11.c +25 -12
- data/lib/puma.rb +7 -4
- data/lib/puma/app/status.rb +44 -46
- data/lib/puma/binder.rb +48 -1
- data/lib/puma/cli.rb +4 -0
- data/lib/puma/client.rb +31 -80
- data/lib/puma/cluster.rb +39 -202
- data/lib/puma/cluster/worker.rb +176 -0
- data/lib/puma/cluster/worker_handle.rb +86 -0
- data/lib/puma/configuration.rb +20 -8
- data/lib/puma/const.rb +11 -3
- data/lib/puma/control_cli.rb +71 -70
- data/lib/puma/dsl.rb +67 -19
- data/lib/puma/error_logger.rb +2 -2
- data/lib/puma/events.rb +21 -3
- data/lib/puma/json.rb +96 -0
- data/lib/puma/launcher.rb +61 -12
- data/lib/puma/minissl.rb +8 -0
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/reactor.rb +79 -373
- data/lib/puma/request.rb +451 -0
- data/lib/puma/runner.rb +15 -21
- data/lib/puma/server.rb +193 -508
- data/lib/puma/single.rb +3 -2
- data/lib/puma/state_file.rb +5 -3
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +22 -2
- data/lib/puma/util.rb +12 -0
- metadata +9 -6
- data/docs/jungle/upstart/README.md +0 -61
- data/docs/jungle/upstart/puma-manager.conf +0 -31
- data/docs/jungle/upstart/puma.conf +0 -69
- data/lib/puma/accept_nonblock.rb +0 -29
    
        data/lib/puma/server.rb
    CHANGED
    
    | @@ -11,6 +11,7 @@ require 'puma/client' | |
| 11 11 | 
             
            require 'puma/binder'
         | 
| 12 12 | 
             
            require 'puma/util'
         | 
| 13 13 | 
             
            require 'puma/io_buffer'
         | 
| 14 | 
            +
            require 'puma/request'
         | 
| 14 15 |  | 
| 15 16 | 
             
            require 'socket'
         | 
| 16 17 | 
             
            require 'forwardable'
         | 
| @@ -30,19 +31,31 @@ module Puma | |
| 30 31 | 
             
              class Server
         | 
| 31 32 |  | 
| 32 33 | 
             
                include Puma::Const
         | 
| 34 | 
            +
                include Request
         | 
| 33 35 | 
             
                extend Forwardable
         | 
| 34 36 |  | 
| 35 37 | 
             
                attr_reader :thread
         | 
| 36 38 | 
             
                attr_reader :events
         | 
| 37 | 
            -
                attr_reader : | 
| 39 | 
            +
                attr_reader :min_threads, :max_threads  # for #stats
         | 
| 40 | 
            +
                attr_reader :requests_count             # @version 5.0.0
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                # @todo the following may be deprecated in the future
         | 
| 43 | 
            +
                attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
         | 
| 44 | 
            +
                  :leak_stack_on_error,
         | 
| 45 | 
            +
                  :persistent_timeout, :reaping_time
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                # @deprecated v6.0.0
         | 
| 48 | 
            +
                attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
         | 
| 49 | 
            +
                  :leak_stack_on_error, :min_threads, :max_threads,
         | 
| 50 | 
            +
                  :persistent_timeout, :reaping_time
         | 
| 51 | 
            +
             | 
| 38 52 | 
             
                attr_accessor :app
         | 
| 53 | 
            +
                attr_accessor :binder
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
         | 
| 56 | 
            +
                  :add_unix_listener, :connected_ports
         | 
| 39 57 |  | 
| 40 | 
            -
                 | 
| 41 | 
            -
                attr_accessor :max_threads
         | 
| 42 | 
            -
                attr_accessor :persistent_timeout
         | 
| 43 | 
            -
                attr_accessor :auto_trim_time
         | 
| 44 | 
            -
                attr_accessor :reaping_time
         | 
| 45 | 
            -
                attr_accessor :first_data_timeout
         | 
| 58 | 
            +
                ThreadLocalKey = :puma_server
         | 
| 46 59 |  | 
| 47 60 | 
             
                # Create a server for the rack app +app+.
         | 
| 48 61 | 
             
                #
         | 
| @@ -52,6 +65,10 @@ module Puma | |
| 52 65 | 
             
                # Server#run returns a thread that you can join on to wait for the server
         | 
| 53 66 | 
             
                # to do its work.
         | 
| 54 67 | 
             
                #
         | 
| 68 | 
            +
                # @note Several instance variables exist so they are available for testing,
         | 
| 69 | 
            +
                #   and have default values set via +fetch+.  Normally the values are set via
         | 
| 70 | 
            +
                #   `::Puma::Configuration.puma_default_options`.
         | 
| 71 | 
            +
                #
         | 
| 55 72 | 
             
                def initialize(app, events=Events.stdio, options={})
         | 
| 56 73 | 
             
                  @app = app
         | 
| 57 74 | 
             
                  @events = events
         | 
| @@ -59,24 +76,26 @@ module Puma | |
| 59 76 | 
             
                  @check, @notify = nil
         | 
| 60 77 | 
             
                  @status = :stop
         | 
| 61 78 |  | 
| 62 | 
            -
                  @min_threads = 0
         | 
| 63 | 
            -
                  @max_threads = 16
         | 
| 64 79 | 
             
                  @auto_trim_time = 30
         | 
| 65 80 | 
             
                  @reaping_time = 1
         | 
| 66 81 |  | 
| 67 82 | 
             
                  @thread = nil
         | 
| 68 83 | 
             
                  @thread_pool = nil
         | 
| 69 | 
            -
                  @early_hints = nil
         | 
| 70 84 |  | 
| 71 | 
            -
                  @ | 
| 72 | 
            -
                  @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
         | 
| 85 | 
            +
                  @options = options
         | 
| 73 86 |  | 
| 74 | 
            -
                  @ | 
| 87 | 
            +
                  @early_hints        = options.fetch :early_hints, nil
         | 
| 88 | 
            +
                  @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
         | 
| 89 | 
            +
                  @min_threads        = options.fetch :min_threads, 0
         | 
| 90 | 
            +
                  @max_threads        = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
         | 
| 91 | 
            +
                  @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
         | 
| 92 | 
            +
                  @queue_requests     = options.fetch :queue_requests, true
         | 
| 93 | 
            +
                  @max_fast_inline    = options.fetch :max_fast_inline, MAX_FAST_INLINE
         | 
| 75 94 |  | 
| 76 | 
            -
                   | 
| 95 | 
            +
                  temp = !!(@options[:environment] =~ /\A(development|test)\z/)
         | 
| 96 | 
            +
                  @leak_stack_on_error = @options[:environment] ? temp : true
         | 
| 77 97 |  | 
| 78 | 
            -
                  @ | 
| 79 | 
            -
                  @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
         | 
| 98 | 
            +
                  @binder = Binder.new(events)
         | 
| 80 99 |  | 
| 81 100 | 
             
                  ENV['RACK_ENV'] ||= "development"
         | 
| 82 101 |  | 
| @@ -87,24 +106,33 @@ module Puma | |
| 87 106 | 
             
                  @requests_count = 0
         | 
| 88 107 | 
             
                end
         | 
| 89 108 |  | 
| 90 | 
            -
                attr_accessor :binder, :leak_stack_on_error, :early_hints
         | 
| 91 | 
            -
             | 
| 92 | 
            -
                def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
         | 
| 93 | 
            -
             | 
| 94 109 | 
             
                def inherit_binder(bind)
         | 
| 95 110 | 
             
                  @binder = bind
         | 
| 96 111 | 
             
                end
         | 
| 97 112 |  | 
| 98 113 | 
             
                class << self
         | 
| 114 | 
            +
                  # @!attribute [r] current
         | 
| 115 | 
            +
                  def current
         | 
| 116 | 
            +
                    Thread.current[ThreadLocalKey]
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
             | 
| 99 119 | 
             
                  # :nodoc:
         | 
| 100 120 | 
             
                  # @version 5.0.0
         | 
| 101 121 | 
             
                  def tcp_cork_supported?
         | 
| 102 122 | 
             
                    RbConfig::CONFIG['host_os'] =~ /linux/ &&
         | 
| 103 123 | 
             
                      Socket.const_defined?(:IPPROTO_TCP) &&
         | 
| 104 | 
            -
                      Socket.const_defined?(:TCP_CORK) | 
| 124 | 
            +
                      Socket.const_defined?(:TCP_CORK)
         | 
| 125 | 
            +
                  end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                  # :nodoc:
         | 
| 128 | 
            +
                  # @version 5.0.0
         | 
| 129 | 
            +
                  def closed_socket_supported?
         | 
| 130 | 
            +
                    RbConfig::CONFIG['host_os'] =~ /linux/ &&
         | 
| 131 | 
            +
                      Socket.const_defined?(:IPPROTO_TCP) &&
         | 
| 105 132 | 
             
                      Socket.const_defined?(:TCP_INFO)
         | 
| 106 133 | 
             
                  end
         | 
| 107 134 | 
             
                  private :tcp_cork_supported?
         | 
| 135 | 
            +
                  private :closed_socket_supported?
         | 
| 108 136 | 
             
                end
         | 
| 109 137 |  | 
| 110 138 | 
             
                # On Linux, use TCP_CORK to better control how the TCP stack
         | 
| @@ -131,7 +159,15 @@ module Puma | |
| 131 159 | 
             
                      Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
         | 
| 132 160 | 
             
                    end
         | 
| 133 161 | 
             
                  end
         | 
| 162 | 
            +
                else
         | 
| 163 | 
            +
                  def cork_socket(socket)
         | 
| 164 | 
            +
                  end
         | 
| 134 165 |  | 
| 166 | 
            +
                  def uncork_socket(socket)
         | 
| 167 | 
            +
                  end
         | 
| 168 | 
            +
                end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                if closed_socket_supported?
         | 
| 135 171 | 
             
                  def closed_socket?(socket)
         | 
| 136 172 | 
             
                    return false unless socket.kind_of? TCPSocket
         | 
| 137 173 | 
             
                    return false unless @precheck_closing
         | 
| @@ -149,21 +185,17 @@ module Puma | |
| 149 185 | 
             
                    end
         | 
| 150 186 | 
             
                  end
         | 
| 151 187 | 
             
                else
         | 
| 152 | 
            -
                  def cork_socket(socket)
         | 
| 153 | 
            -
                  end
         | 
| 154 | 
            -
             | 
| 155 | 
            -
                  def uncork_socket(socket)
         | 
| 156 | 
            -
                  end
         | 
| 157 | 
            -
             | 
| 158 188 | 
             
                  def closed_socket?(socket)
         | 
| 159 189 | 
             
                    false
         | 
| 160 190 | 
             
                  end
         | 
| 161 191 | 
             
                end
         | 
| 162 192 |  | 
| 193 | 
            +
                # @!attribute [r] backlog
         | 
| 163 194 | 
             
                def backlog
         | 
| 164 195 | 
             
                  @thread_pool and @thread_pool.backlog
         | 
| 165 196 | 
             
                end
         | 
| 166 197 |  | 
| 198 | 
            +
                # @!attribute [r] running
         | 
| 167 199 | 
             
                def running
         | 
| 168 200 | 
             
                  @thread_pool and @thread_pool.spawned
         | 
| 169 201 | 
             
                end
         | 
| @@ -176,6 +208,7 @@ module Puma | |
| 176 208 | 
             
                # there are 5 threads sitting idle ready to take
         | 
| 177 209 | 
             
                # a request. If one request comes in, then the
         | 
| 178 210 | 
             
                # value would be 4 until it finishes processing.
         | 
| 211 | 
            +
                # @!attribute [r] pool_capacity
         | 
| 179 212 | 
             
                def pool_capacity
         | 
| 180 213 | 
             
                  @thread_pool and @thread_pool.pool_capacity
         | 
| 181 214 | 
             
                end
         | 
| @@ -186,64 +219,26 @@ module Puma | |
| 186 219 | 
             
                # up in the background to handle requests. Otherwise requests
         | 
| 187 220 | 
             
                # are handled synchronously.
         | 
| 188 221 | 
             
                #
         | 
| 189 | 
            -
                def run(background=true)
         | 
| 222 | 
            +
                def run(background=true, thread_name: 'server')
         | 
| 190 223 | 
             
                  BasicSocket.do_not_reverse_lookup = true
         | 
| 191 224 |  | 
| 192 225 | 
             
                  @events.fire :state, :booting
         | 
| 193 226 |  | 
| 194 227 | 
             
                  @status = :run
         | 
| 195 228 |  | 
| 196 | 
            -
                  @thread_pool = ThreadPool.new( | 
| 197 | 
            -
             | 
| 198 | 
            -
             | 
| 199 | 
            -
             | 
| 200 | 
            -
                     | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 203 | 
            -
                    process_now = false
         | 
| 204 | 
            -
             | 
| 205 | 
            -
                    begin
         | 
| 206 | 
            -
                      if @queue_requests
         | 
| 207 | 
            -
                        process_now = client.eagerly_finish
         | 
| 208 | 
            -
                      else
         | 
| 209 | 
            -
                        client.finish(@first_data_timeout)
         | 
| 210 | 
            -
                        process_now = true
         | 
| 211 | 
            -
                      end
         | 
| 212 | 
            -
                    rescue MiniSSL::SSLError => e
         | 
| 213 | 
            -
                      ssl_socket = client.io
         | 
| 214 | 
            -
                      addr = ssl_socket.peeraddr.last
         | 
| 215 | 
            -
                      cert = ssl_socket.peercert
         | 
| 216 | 
            -
             | 
| 217 | 
            -
                      client.close
         | 
| 218 | 
            -
             | 
| 219 | 
            -
                      @events.ssl_error e, addr, cert
         | 
| 220 | 
            -
                    rescue HttpParserError => e
         | 
| 221 | 
            -
                      client.write_error(400)
         | 
| 222 | 
            -
                      client.close
         | 
| 223 | 
            -
             | 
| 224 | 
            -
                      @events.parse_error e, client
         | 
| 225 | 
            -
                    rescue ConnectionError, EOFError => e
         | 
| 226 | 
            -
                      client.close
         | 
| 227 | 
            -
             | 
| 228 | 
            -
                      @events.connection_error e, client
         | 
| 229 | 
            -
                    else
         | 
| 230 | 
            -
                      if process_now
         | 
| 231 | 
            -
                        process_client client, buffer
         | 
| 232 | 
            -
                      else
         | 
| 233 | 
            -
                        client.set_timeout @first_data_timeout
         | 
| 234 | 
            -
                        @reactor.add client
         | 
| 235 | 
            -
                      end
         | 
| 236 | 
            -
                    end
         | 
| 237 | 
            -
             | 
| 238 | 
            -
                    process_now
         | 
| 239 | 
            -
                  end
         | 
| 229 | 
            +
                  @thread_pool = ThreadPool.new(
         | 
| 230 | 
            +
                    @min_threads,
         | 
| 231 | 
            +
                    @max_threads,
         | 
| 232 | 
            +
                    ::Puma::IOBuffer,
         | 
| 233 | 
            +
                    &method(:process_client)
         | 
| 234 | 
            +
                  )
         | 
| 240 235 |  | 
| 241 236 | 
             
                  @thread_pool.out_of_band_hook = @options[:out_of_band]
         | 
| 242 237 | 
             
                  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
         | 
| 243 238 |  | 
| 244 239 | 
             
                  if @queue_requests
         | 
| 245 | 
            -
                    @reactor = Reactor.new | 
| 246 | 
            -
                    @reactor. | 
| 240 | 
            +
                    @reactor = Reactor.new(&method(:reactor_wakeup))
         | 
| 241 | 
            +
                    @reactor.run
         | 
| 247 242 | 
             
                  end
         | 
| 248 243 |  | 
| 249 244 | 
             
                  if @reaping_time
         | 
| @@ -254,11 +249,13 @@ module Puma | |
| 254 249 | 
             
                    @thread_pool.auto_trim!(@auto_trim_time)
         | 
| 255 250 | 
             
                  end
         | 
| 256 251 |  | 
| 252 | 
            +
                  @check, @notify = Puma::Util.pipe unless @notify
         | 
| 253 | 
            +
             | 
| 257 254 | 
             
                  @events.fire :state, :running
         | 
| 258 255 |  | 
| 259 256 | 
             
                  if background
         | 
| 260 257 | 
             
                    @thread = Thread.new do
         | 
| 261 | 
            -
                      Puma.set_thread_name  | 
| 258 | 
            +
                      Puma.set_thread_name thread_name
         | 
| 262 259 | 
             
                      handle_servers
         | 
| 263 260 | 
             
                    end
         | 
| 264 261 | 
             
                    return @thread
         | 
| @@ -267,8 +264,45 @@ module Puma | |
| 267 264 | 
             
                  end
         | 
| 268 265 | 
             
                end
         | 
| 269 266 |  | 
| 267 | 
            +
                # This method is called from the Reactor thread when a queued Client receives data,
         | 
| 268 | 
            +
                # times out, or when the Reactor is shutting down.
         | 
| 269 | 
            +
                #
         | 
| 270 | 
            +
                # It is responsible for ensuring that a request has been completely received
         | 
| 271 | 
            +
                # before it starts to be processed by the ThreadPool. This may be known as read buffering.
         | 
| 272 | 
            +
                # If read buffering is not done, and no other read buffering is performed (such as by an application server
         | 
| 273 | 
            +
                # such as nginx) then the application would be subject to a slow client attack.
         | 
| 274 | 
            +
                #
         | 
| 275 | 
            +
                # For a graphical representation of how the request buffer works see [architecture.md](https://github.com/puma/puma/blob/master/docs/architecture.md#connection-pipeline).
         | 
| 276 | 
            +
                #
         | 
| 277 | 
            +
                # The method checks to see if it has the full header and body with
         | 
| 278 | 
            +
                # the `Puma::Client#try_to_finish` method. If the full request has been sent,
         | 
| 279 | 
            +
                # then the request is passed to the ThreadPool (`@thread_pool << client`)
         | 
| 280 | 
            +
                # so that a "worker thread" can pick up the request and begin to execute application logic.
         | 
| 281 | 
            +
                # The Client is then removed from the reactor (return `true`).
         | 
| 282 | 
            +
                #
         | 
| 283 | 
            +
                # If a client object times out, a 408 response is written, its connection is closed,
         | 
| 284 | 
            +
                # and the object is removed from the reactor (return `true`).
         | 
| 285 | 
            +
                #
         | 
| 286 | 
            +
                # If the Reactor is shutting down, all Clients are either timed out or passed to the
         | 
| 287 | 
            +
                # ThreadPool, depending on their current state (#can_close?).
         | 
| 288 | 
            +
                #
         | 
| 289 | 
            +
                # Otherwise, if the full request is not ready then the client will remain in the reactor
         | 
| 290 | 
            +
                # (return `false`). When the client sends more data to the socket the `Puma::Client` object
         | 
| 291 | 
            +
                # will wake up and again be checked to see if it's ready to be passed to the thread pool.
         | 
| 292 | 
            +
                def reactor_wakeup(client)
         | 
| 293 | 
            +
                  shutdown = !@queue_requests
         | 
| 294 | 
            +
                  if client.try_to_finish || (shutdown && !client.can_close?)
         | 
| 295 | 
            +
                    @thread_pool << client
         | 
| 296 | 
            +
                  elsif shutdown || client.timeout == 0
         | 
| 297 | 
            +
                    client.timeout!
         | 
| 298 | 
            +
                  end
         | 
| 299 | 
            +
                rescue StandardError => e
         | 
| 300 | 
            +
                  client_error(e, client)
         | 
| 301 | 
            +
                  client.close
         | 
| 302 | 
            +
                  true
         | 
| 303 | 
            +
                end
         | 
| 304 | 
            +
             | 
| 270 305 | 
             
                def handle_servers
         | 
| 271 | 
            -
                  @check, @notify = Puma::Util.pipe unless @notify
         | 
| 272 306 | 
             
                  begin
         | 
| 273 307 | 
             
                    check = @check
         | 
| 274 308 | 
             
                    sockets = [check] + @binder.ios
         | 
| @@ -319,7 +353,6 @@ module Puma | |
| 319 353 |  | 
| 320 354 | 
             
                    if queue_requests
         | 
| 321 355 | 
             
                      @queue_requests = false
         | 
| 322 | 
            -
                      @reactor.clear!
         | 
| 323 356 | 
             
                      @reactor.shutdown
         | 
| 324 357 | 
             
                    end
         | 
| 325 358 | 
             
                    graceful_shutdown if @status == :stop || @status == :restart
         | 
| @@ -359,27 +392,48 @@ module Puma | |
| 359 392 | 
             
                  return false
         | 
| 360 393 | 
             
                end
         | 
| 361 394 |  | 
| 362 | 
            -
                # Given a connection on +client+, handle the incoming requests | 
| 395 | 
            +
                # Given a connection on +client+, handle the incoming requests,
         | 
| 396 | 
            +
                # or queue the connection in the Reactor if no request is available.
         | 
| 397 | 
            +
                #
         | 
| 398 | 
            +
                # This method is called from a ThreadPool worker thread.
         | 
| 363 399 | 
             
                #
         | 
| 364 | 
            -
                # This method  | 
| 400 | 
            +
                # This method supports HTTP Keep-Alive so it may, depending on if the client
         | 
| 365 401 | 
             
                # indicates that it supports keep alive, wait for another request before
         | 
| 366 402 | 
             
                # returning.
         | 
| 367 403 | 
             
                #
         | 
| 404 | 
            +
                # Return true if one or more requests were processed.
         | 
| 368 405 | 
             
                def process_client(client, buffer)
         | 
| 406 | 
            +
                  # Advertise this server into the thread
         | 
| 407 | 
            +
                  Thread.current[ThreadLocalKey] = self
         | 
| 408 | 
            +
             | 
| 409 | 
            +
                  clean_thread_locals = @options[:clean_thread_locals]
         | 
| 410 | 
            +
                  close_socket = true
         | 
| 411 | 
            +
             | 
| 412 | 
            +
                  requests = 0
         | 
| 413 | 
            +
             | 
| 369 414 | 
             
                  begin
         | 
| 415 | 
            +
                    if @queue_requests &&
         | 
| 416 | 
            +
                      !client.eagerly_finish
         | 
| 370 417 |  | 
| 371 | 
            -
             | 
| 372 | 
            -
             | 
| 418 | 
            +
                      client.set_timeout(@first_data_timeout)
         | 
| 419 | 
            +
                      if @reactor.add client
         | 
| 420 | 
            +
                        close_socket = false
         | 
| 421 | 
            +
                        return false
         | 
| 422 | 
            +
                      end
         | 
| 423 | 
            +
                    end
         | 
| 373 424 |  | 
| 374 | 
            -
                     | 
| 425 | 
            +
                    with_force_shutdown(client) do
         | 
| 426 | 
            +
                      client.finish(@first_data_timeout)
         | 
| 427 | 
            +
                    end
         | 
| 375 428 |  | 
| 376 429 | 
             
                    while true
         | 
| 430 | 
            +
                      @requests_count += 1
         | 
| 377 431 | 
             
                      case handle_request(client, buffer)
         | 
| 378 432 | 
             
                      when false
         | 
| 379 | 
            -
                         | 
| 433 | 
            +
                        break
         | 
| 380 434 | 
             
                      when :async
         | 
| 381 435 | 
             
                        close_socket = false
         | 
| 382 | 
            -
                         | 
| 436 | 
            +
                        break
         | 
| 383 437 | 
             
                      when true
         | 
| 384 438 | 
             
                        buffer.reset
         | 
| 385 439 |  | 
| @@ -389,55 +443,33 @@ module Puma | |
| 389 443 |  | 
| 390 444 | 
             
                        check_for_more_data = @status == :run
         | 
| 391 445 |  | 
| 392 | 
            -
                        if requests >=  | 
| 446 | 
            +
                        if requests >= @max_fast_inline
         | 
| 393 447 | 
             
                          # This will mean that reset will only try to use the data it already
         | 
| 394 448 | 
             
                          # has buffered and won't try to read more data. What this means is that
         | 
| 395 449 | 
             
                          # every client, independent of their request speed, gets treated like a slow
         | 
| 396 | 
            -
                          # one once every  | 
| 450 | 
            +
                          # one once every max_fast_inline requests.
         | 
| 397 451 | 
             
                          check_for_more_data = false
         | 
| 398 452 | 
             
                        end
         | 
| 399 453 |  | 
| 400 | 
            -
                         | 
| 401 | 
            -
                           | 
| 402 | 
            -
             | 
| 454 | 
            +
                        next_request_ready = with_force_shutdown(client) do
         | 
| 455 | 
            +
                          client.reset(check_for_more_data)
         | 
| 456 | 
            +
                        end
         | 
| 457 | 
            +
             | 
| 458 | 
            +
                        unless next_request_ready
         | 
| 459 | 
            +
                          break unless @queue_requests
         | 
| 403 460 | 
             
                          client.set_timeout @persistent_timeout
         | 
| 404 | 
            -
                          @reactor.add client
         | 
| 405 | 
            -
             | 
| 461 | 
            +
                          if @reactor.add client
         | 
| 462 | 
            +
                            close_socket = false
         | 
| 463 | 
            +
                            break
         | 
| 464 | 
            +
                          end
         | 
| 406 465 | 
             
                        end
         | 
| 407 466 | 
             
                      end
         | 
| 408 467 | 
             
                    end
         | 
| 409 | 
            -
             | 
| 410 | 
            -
                  # The client disconnected while we were reading data
         | 
| 411 | 
            -
                  rescue ConnectionError
         | 
| 412 | 
            -
                    # Swallow them. The ensure tries to close +client+ down
         | 
| 413 | 
            -
             | 
| 414 | 
            -
                  # SSL handshake error
         | 
| 415 | 
            -
                  rescue MiniSSL::SSLError => e
         | 
| 416 | 
            -
                    lowlevel_error(e, client.env)
         | 
| 417 | 
            -
             | 
| 418 | 
            -
                    ssl_socket = client.io
         | 
| 419 | 
            -
                    addr = ssl_socket.peeraddr.last
         | 
| 420 | 
            -
                    cert = ssl_socket.peercert
         | 
| 421 | 
            -
             | 
| 422 | 
            -
                    close_socket = true
         | 
| 423 | 
            -
             | 
| 424 | 
            -
                    @events.ssl_error e, addr, cert
         | 
| 425 | 
            -
             | 
| 426 | 
            -
                  # The client doesn't know HTTP well
         | 
| 427 | 
            -
                  rescue HttpParserError => e
         | 
| 428 | 
            -
                    lowlevel_error(e, client.env)
         | 
| 429 | 
            -
             | 
| 430 | 
            -
                    client.write_error(400)
         | 
| 431 | 
            -
             | 
| 432 | 
            -
                    @events.parse_error e, client
         | 
| 433 | 
            -
             | 
| 434 | 
            -
                  # Server error
         | 
| 468 | 
            +
                    true
         | 
| 435 469 | 
             
                  rescue StandardError => e
         | 
| 436 | 
            -
                     | 
| 437 | 
            -
             | 
| 438 | 
            -
                     | 
| 439 | 
            -
             | 
| 440 | 
            -
                    @events.unknown_error e, nil, "Read"
         | 
| 470 | 
            +
                    client_error(e, client)
         | 
| 471 | 
            +
                    # The ensure tries to close +client+ down
         | 
| 472 | 
            +
                    requests > 0
         | 
| 441 473 | 
             
                  ensure
         | 
| 442 474 | 
             
                    buffer.reset
         | 
| 443 475 |  | 
| @@ -452,348 +484,15 @@ module Puma | |
| 452 484 | 
             
                  end
         | 
| 453 485 | 
             
                end
         | 
| 454 486 |  | 
| 455 | 
            -
                #  | 
| 456 | 
            -
                #  | 
| 457 | 
            -
                 | 
| 458 | 
            -
             | 
| 459 | 
            -
             | 
| 460 | 
            -
             | 
| 461 | 
            -
                      env[SERVER_NAME] = host[0, colon]
         | 
| 462 | 
            -
                      env[SERVER_PORT] = host[colon+1, host.bytesize]
         | 
| 463 | 
            -
                    else
         | 
| 464 | 
            -
                      env[SERVER_NAME] = host
         | 
| 465 | 
            -
                      env[SERVER_PORT] = default_server_port(env)
         | 
| 466 | 
            -
                    end
         | 
| 467 | 
            -
                  else
         | 
| 468 | 
            -
                    env[SERVER_NAME] = LOCALHOST
         | 
| 469 | 
            -
                    env[SERVER_PORT] = default_server_port(env)
         | 
| 470 | 
            -
                  end
         | 
| 471 | 
            -
             | 
| 472 | 
            -
                  unless env[REQUEST_PATH]
         | 
| 473 | 
            -
                    # it might be a dumbass full host request header
         | 
| 474 | 
            -
                    uri = URI.parse(env[REQUEST_URI])
         | 
| 475 | 
            -
                    env[REQUEST_PATH] = uri.path
         | 
| 476 | 
            -
             | 
| 477 | 
            -
                    raise "No REQUEST PATH" unless env[REQUEST_PATH]
         | 
| 478 | 
            -
             | 
| 479 | 
            -
                    # A nil env value will cause a LintError (and fatal errors elsewhere),
         | 
| 480 | 
            -
                    # so only set the env value if there actually is a value.
         | 
| 481 | 
            -
                    env[QUERY_STRING] = uri.query if uri.query
         | 
| 482 | 
            -
                  end
         | 
| 483 | 
            -
             | 
| 484 | 
            -
                  env[PATH_INFO] = env[REQUEST_PATH]
         | 
| 485 | 
            -
             | 
| 486 | 
            -
                  # From https://www.ietf.org/rfc/rfc3875 :
         | 
| 487 | 
            -
                  # "Script authors should be aware that the REMOTE_ADDR and
         | 
| 488 | 
            -
                  # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
         | 
| 489 | 
            -
                  # may not identify the ultimate source of the request.
         | 
| 490 | 
            -
                  # They identify the client for the immediate request to the
         | 
| 491 | 
            -
                  # server; that client may be a proxy, gateway, or other
         | 
| 492 | 
            -
                  # intermediary acting on behalf of the actual source client."
         | 
| 493 | 
            -
                  #
         | 
| 494 | 
            -
             | 
| 495 | 
            -
                  unless env.key?(REMOTE_ADDR)
         | 
| 496 | 
            -
                    begin
         | 
| 497 | 
            -
                      addr = client.peerip
         | 
| 498 | 
            -
                    rescue Errno::ENOTCONN
         | 
| 499 | 
            -
                      # Client disconnects can result in an inability to get the
         | 
| 500 | 
            -
                      # peeraddr from the socket; default to localhost.
         | 
| 501 | 
            -
                      addr = LOCALHOST_IP
         | 
| 502 | 
            -
                    end
         | 
| 503 | 
            -
             | 
| 504 | 
            -
                    # Set unix socket addrs to localhost
         | 
| 505 | 
            -
                    addr = LOCALHOST_IP if addr.empty?
         | 
| 506 | 
            -
             | 
| 507 | 
            -
                    env[REMOTE_ADDR] = addr
         | 
| 508 | 
            -
                  end
         | 
| 509 | 
            -
                end
         | 
| 510 | 
            -
             | 
| 511 | 
            -
                def default_server_port(env)
         | 
| 512 | 
            -
                  if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
         | 
| 513 | 
            -
                    PORT_443
         | 
| 514 | 
            -
                  else
         | 
| 515 | 
            -
                    PORT_80
         | 
| 516 | 
            -
                  end
         | 
| 517 | 
            -
                end
         | 
| 518 | 
            -
             | 
| 519 | 
            -
                # Takes the request +req+, invokes the Rack application to construct
         | 
| 520 | 
            -
                # the response and writes it back to +req.io+.
         | 
| 521 | 
            -
                #
         | 
| 522 | 
            -
                # The second parameter +lines+ is a IO-like object unique to this thread.
         | 
| 523 | 
            -
                # This is normally an instance of Puma::IOBuffer.
         | 
| 524 | 
            -
                #
         | 
| 525 | 
            -
                # It'll return +false+ when the connection is closed, this doesn't mean
         | 
| 526 | 
            -
                # that the response wasn't successful.
         | 
| 527 | 
            -
                #
         | 
| 528 | 
            -
                # It'll return +:async+ if the connection remains open but will be handled
         | 
| 529 | 
            -
                # elsewhere, i.e. the connection has been hijacked by the Rack application.
         | 
| 530 | 
            -
                #
         | 
| 531 | 
            -
                # Finally, it'll return +true+ on keep-alive connections.
         | 
| 532 | 
            -
                def handle_request(req, lines)
         | 
| 533 | 
            -
                  @requests_count +=1
         | 
| 534 | 
            -
             | 
| 535 | 
            -
                  env = req.env
         | 
| 536 | 
            -
                  client = req.io
         | 
| 537 | 
            -
             | 
| 538 | 
            -
                  return false if closed_socket?(client)
         | 
| 539 | 
            -
             | 
| 540 | 
            -
                  normalize_env env, req
         | 
| 541 | 
            -
             | 
| 542 | 
            -
                  env[PUMA_SOCKET] = client
         | 
| 543 | 
            -
             | 
| 544 | 
            -
                  if env[HTTPS_KEY] && client.peercert
         | 
| 545 | 
            -
                    env[PUMA_PEERCERT] = client.peercert
         | 
| 546 | 
            -
                  end
         | 
| 547 | 
            -
             | 
| 548 | 
            -
                  env[HIJACK_P] = true
         | 
| 549 | 
            -
                  env[HIJACK] = req
         | 
| 550 | 
            -
             | 
| 551 | 
            -
                  body = req.body
         | 
| 552 | 
            -
             | 
| 553 | 
            -
                  head = env[REQUEST_METHOD] == HEAD
         | 
| 554 | 
            -
             | 
| 555 | 
            -
                  env[RACK_INPUT] = body
         | 
| 556 | 
            -
                  env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
         | 
| 557 | 
            -
             | 
| 558 | 
            -
                  if @early_hints
         | 
| 559 | 
            -
                    env[EARLY_HINTS] = lambda { |headers|
         | 
| 560 | 
            -
                      begin
         | 
| 561 | 
            -
                        fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
         | 
| 562 | 
            -
             | 
| 563 | 
            -
                        headers.each_pair do |k, vs|
         | 
| 564 | 
            -
                          if vs.respond_to?(:to_s) && !vs.to_s.empty?
         | 
| 565 | 
            -
                            vs.to_s.split(NEWLINE).each do |v|
         | 
| 566 | 
            -
                              next if possible_header_injection?(v)
         | 
| 567 | 
            -
                              fast_write client, "#{k}: #{v}\r\n"
         | 
| 568 | 
            -
                            end
         | 
| 569 | 
            -
                          else
         | 
| 570 | 
            -
                            fast_write client, "#{k}: #{vs}\r\n"
         | 
| 571 | 
            -
                          end
         | 
| 572 | 
            -
                        end
         | 
| 573 | 
            -
             | 
| 574 | 
            -
                        fast_write client, "\r\n".freeze
         | 
| 575 | 
            -
                      rescue ConnectionError => e
         | 
| 576 | 
            -
                        @events.debug_error e
         | 
| 577 | 
            -
                        # noop, if we lost the socket we just won't send the early hints
         | 
| 578 | 
            -
                      end
         | 
| 579 | 
            -
                    }
         | 
| 580 | 
            -
                  end
         | 
| 581 | 
            -
             | 
| 582 | 
            -
                  # Fixup any headers with , in the name to have _ now. We emit
         | 
| 583 | 
            -
                  # headers with , in them during the parse phase to avoid ambiguity
         | 
| 584 | 
            -
                  # with the - to _ conversion for critical headers. But here for
         | 
| 585 | 
            -
                  # compatibility, we'll convert them back. This code is written to
         | 
| 586 | 
            -
                  # avoid allocation in the common case (ie there are no headers
         | 
| 587 | 
            -
                  # with , in their names), that's why it has the extra conditionals.
         | 
| 588 | 
            -
             | 
| 589 | 
            -
                  to_delete = nil
         | 
| 590 | 
            -
                  to_add = nil
         | 
| 591 | 
            -
             | 
| 592 | 
            -
                  env.each do |k,v|
         | 
| 593 | 
            -
                    if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
         | 
| 594 | 
            -
                      if to_delete
         | 
| 595 | 
            -
                        to_delete << k
         | 
| 596 | 
            -
                      else
         | 
| 597 | 
            -
                        to_delete = [k]
         | 
| 598 | 
            -
                      end
         | 
| 599 | 
            -
             | 
| 600 | 
            -
                      unless to_add
         | 
| 601 | 
            -
                        to_add = {}
         | 
| 602 | 
            -
                      end
         | 
| 603 | 
            -
             | 
| 604 | 
            -
                      to_add[k.tr(",", "_")] = v
         | 
| 605 | 
            -
                    end
         | 
| 606 | 
            -
                  end
         | 
| 607 | 
            -
             | 
| 608 | 
            -
                  if to_delete
         | 
| 609 | 
            -
                    to_delete.each { |k| env.delete(k) }
         | 
| 610 | 
            -
                    env.merge! to_add
         | 
| 611 | 
            -
                  end
         | 
| 612 | 
            -
             | 
| 613 | 
            -
                  # A rack extension. If the app writes #call'ables to this
         | 
| 614 | 
            -
                  # array, we will invoke them when the request is done.
         | 
| 615 | 
            -
                  #
         | 
| 616 | 
            -
                  after_reply = env[RACK_AFTER_REPLY] = []
         | 
| 617 | 
            -
             | 
| 618 | 
            -
                  begin
         | 
| 619 | 
            -
                    begin
         | 
| 620 | 
            -
                      status, headers, res_body = @app.call(env)
         | 
| 621 | 
            -
             | 
| 622 | 
            -
                      return :async if req.hijacked
         | 
| 623 | 
            -
             | 
| 624 | 
            -
                      status = status.to_i
         | 
| 625 | 
            -
             | 
| 626 | 
            -
                      if status == -1
         | 
| 627 | 
            -
                        unless headers.empty? and res_body == []
         | 
| 628 | 
            -
                          raise "async response must have empty headers and body"
         | 
| 629 | 
            -
                        end
         | 
| 630 | 
            -
             | 
| 631 | 
            -
                        return :async
         | 
| 632 | 
            -
                      end
         | 
| 633 | 
            -
                    rescue ThreadPool::ForceShutdown => e
         | 
| 634 | 
            -
                      @events.unknown_error e, req, "Rack app"
         | 
| 635 | 
            -
                      @events.log "Detected force shutdown of a thread"
         | 
| 636 | 
            -
             | 
| 637 | 
            -
                      status, headers, res_body = lowlevel_error(e, env, 503)
         | 
| 638 | 
            -
                    rescue Exception => e
         | 
| 639 | 
            -
                      @events.unknown_error e, req, "Rack app"
         | 
| 640 | 
            -
             | 
| 641 | 
            -
                      status, headers, res_body = lowlevel_error(e, env, 500)
         | 
| 642 | 
            -
                    end
         | 
| 643 | 
            -
             | 
| 644 | 
            -
                    content_length = nil
         | 
| 645 | 
            -
                    no_body = head
         | 
| 646 | 
            -
             | 
| 647 | 
            -
                    if res_body.kind_of? Array and res_body.size == 1
         | 
| 648 | 
            -
                      content_length = res_body[0].bytesize
         | 
| 649 | 
            -
                    end
         | 
| 650 | 
            -
             | 
| 651 | 
            -
                    cork_socket client
         | 
| 652 | 
            -
             | 
| 653 | 
            -
                    line_ending = LINE_END
         | 
| 654 | 
            -
                    colon = COLON
         | 
| 655 | 
            -
             | 
| 656 | 
            -
                    http_11 = env[HTTP_VERSION] == HTTP_11
         | 
| 657 | 
            -
                    if http_11
         | 
| 658 | 
            -
                      allow_chunked = true
         | 
| 659 | 
            -
                      keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
         | 
| 660 | 
            -
             | 
| 661 | 
            -
                      # An optimization. The most common response is 200, so we can
         | 
| 662 | 
            -
                      # reply with the proper 200 status without having to compute
         | 
| 663 | 
            -
                      # the response header.
         | 
| 664 | 
            -
                      #
         | 
| 665 | 
            -
                      if status == 200
         | 
| 666 | 
            -
                        lines << HTTP_11_200
         | 
| 667 | 
            -
                      else
         | 
| 668 | 
            -
                        lines.append "HTTP/1.1 ", status.to_s, " ",
         | 
| 669 | 
            -
                                     fetch_status_code(status), line_ending
         | 
| 670 | 
            -
             | 
| 671 | 
            -
                        no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
         | 
| 672 | 
            -
                      end
         | 
| 673 | 
            -
                    else
         | 
| 674 | 
            -
                      allow_chunked = false
         | 
| 675 | 
            -
                      keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
         | 
| 676 | 
            -
             | 
| 677 | 
            -
                      # Same optimization as above for HTTP/1.1
         | 
| 678 | 
            -
                      #
         | 
| 679 | 
            -
                      if status == 200
         | 
| 680 | 
            -
                        lines << HTTP_10_200
         | 
| 681 | 
            -
                      else
         | 
| 682 | 
            -
                        lines.append "HTTP/1.0 ", status.to_s, " ",
         | 
| 683 | 
            -
                                     fetch_status_code(status), line_ending
         | 
| 684 | 
            -
             | 
| 685 | 
            -
                        no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
         | 
| 686 | 
            -
                      end
         | 
| 687 | 
            -
                    end
         | 
| 688 | 
            -
             | 
| 689 | 
            -
                    # regardless of what the client wants, we always close the connection
         | 
| 690 | 
            -
                    # if running without request queueing
         | 
| 691 | 
            -
                    keep_alive &&= @queue_requests
         | 
| 692 | 
            -
             | 
| 693 | 
            -
                    response_hijack = nil
         | 
| 694 | 
            -
             | 
| 695 | 
            -
                    headers.each do |k, vs|
         | 
| 696 | 
            -
                      case k.downcase
         | 
| 697 | 
            -
                      when CONTENT_LENGTH2
         | 
| 698 | 
            -
                        next if possible_header_injection?(vs)
         | 
| 699 | 
            -
                        content_length = vs
         | 
| 700 | 
            -
                        next
         | 
| 701 | 
            -
                      when TRANSFER_ENCODING
         | 
| 702 | 
            -
                        allow_chunked = false
         | 
| 703 | 
            -
                        content_length = nil
         | 
| 704 | 
            -
                      when HIJACK
         | 
| 705 | 
            -
                        response_hijack = vs
         | 
| 706 | 
            -
                        next
         | 
| 707 | 
            -
                      end
         | 
| 708 | 
            -
             | 
| 709 | 
            -
                      if vs.respond_to?(:to_s) && !vs.to_s.empty?
         | 
| 710 | 
            -
                        vs.to_s.split(NEWLINE).each do |v|
         | 
| 711 | 
            -
                          next if possible_header_injection?(v)
         | 
| 712 | 
            -
                          lines.append k, colon, v, line_ending
         | 
| 713 | 
            -
                        end
         | 
| 714 | 
            -
                      else
         | 
| 715 | 
            -
                        lines.append k, colon, line_ending
         | 
| 716 | 
            -
                      end
         | 
| 717 | 
            -
                    end
         | 
| 718 | 
            -
             | 
| 719 | 
            -
                    # HTTP/1.1 & 1.0 assume different defaults:
         | 
| 720 | 
            -
                    # - HTTP 1.0 assumes the connection will be closed if not specified
         | 
| 721 | 
            -
                    # - HTTP 1.1 assumes the connection will be kept alive if not specified.
         | 
| 722 | 
            -
                    # Only set the header if we're doing something which is not the default
         | 
| 723 | 
            -
                    # for this protocol version
         | 
| 724 | 
            -
                    if http_11
         | 
| 725 | 
            -
                      lines << CONNECTION_CLOSE if !keep_alive
         | 
| 726 | 
            -
                    else
         | 
| 727 | 
            -
                      lines << CONNECTION_KEEP_ALIVE if keep_alive
         | 
| 728 | 
            -
                    end
         | 
| 729 | 
            -
             | 
| 730 | 
            -
                    if no_body
         | 
| 731 | 
            -
                      if content_length and status != 204
         | 
| 732 | 
            -
                        lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
         | 
| 733 | 
            -
                      end
         | 
| 734 | 
            -
             | 
| 735 | 
            -
                      lines << line_ending
         | 
| 736 | 
            -
                      fast_write client, lines.to_s
         | 
| 737 | 
            -
                      return keep_alive
         | 
| 738 | 
            -
                    end
         | 
| 739 | 
            -
             | 
| 740 | 
            -
                    if content_length
         | 
| 741 | 
            -
                      lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
         | 
| 742 | 
            -
                      chunked = false
         | 
| 743 | 
            -
                    elsif !response_hijack and allow_chunked
         | 
| 744 | 
            -
                      lines << TRANSFER_ENCODING_CHUNKED
         | 
| 745 | 
            -
                      chunked = true
         | 
| 746 | 
            -
                    end
         | 
| 747 | 
            -
             | 
| 748 | 
            -
                    lines << line_ending
         | 
| 749 | 
            -
             | 
| 750 | 
            -
                    fast_write client, lines.to_s
         | 
| 751 | 
            -
             | 
| 752 | 
            -
                    if response_hijack
         | 
| 753 | 
            -
                      response_hijack.call client
         | 
| 754 | 
            -
                      return :async
         | 
| 755 | 
            -
                    end
         | 
| 756 | 
            -
             | 
| 757 | 
            -
                    begin
         | 
| 758 | 
            -
                      res_body.each do |part|
         | 
| 759 | 
            -
                        next if part.bytesize.zero?
         | 
| 760 | 
            -
                        if chunked
         | 
| 761 | 
            -
                          fast_write client, part.bytesize.to_s(16)
         | 
| 762 | 
            -
                          fast_write client, line_ending
         | 
| 763 | 
            -
                          fast_write client, part
         | 
| 764 | 
            -
                          fast_write client, line_ending
         | 
| 765 | 
            -
                        else
         | 
| 766 | 
            -
                          fast_write client, part
         | 
| 767 | 
            -
                        end
         | 
| 768 | 
            -
             | 
| 769 | 
            -
                        client.flush
         | 
| 770 | 
            -
                      end
         | 
| 771 | 
            -
             | 
| 772 | 
            -
                      if chunked
         | 
| 773 | 
            -
                        fast_write client, CLOSE_CHUNKED
         | 
| 774 | 
            -
                        client.flush
         | 
| 775 | 
            -
                      end
         | 
| 776 | 
            -
                    rescue SystemCallError, IOError
         | 
| 777 | 
            -
                      raise ConnectionError, "Connection error detected during write"
         | 
| 778 | 
            -
                    end
         | 
| 779 | 
            -
             | 
| 780 | 
            -
                  ensure
         | 
| 781 | 
            -
                    uncork_socket client
         | 
| 782 | 
            -
             | 
| 783 | 
            -
                    body.close
         | 
| 784 | 
            -
                    req.tempfile.unlink if req.tempfile
         | 
| 785 | 
            -
                    res_body.close if res_body.respond_to? :close
         | 
| 786 | 
            -
             | 
| 787 | 
            -
                    after_reply.each { |o| o.call }
         | 
| 788 | 
            -
                  end
         | 
| 789 | 
            -
             | 
| 790 | 
            -
                  return keep_alive
         | 
| 487 | 
            +
                # Triggers a client timeout if the thread-pool shuts down
         | 
| 488 | 
            +
                # during execution of the provided block.
         | 
| 489 | 
            +
                def with_force_shutdown(client, &block)
         | 
| 490 | 
            +
                  @thread_pool.with_force_shutdown(&block)
         | 
| 491 | 
            +
                rescue ThreadPool::ForceShutdown
         | 
| 492 | 
            +
                  client.timeout!
         | 
| 791 493 | 
             
                end
         | 
| 792 494 |  | 
| 793 | 
            -
                 | 
| 794 | 
            -
                  HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
         | 
| 795 | 
            -
                end
         | 
| 796 | 
            -
                private :fetch_status_code
         | 
| 495 | 
            +
                # :nocov:
         | 
| 797 496 |  | 
| 798 497 | 
             
                # Given the request +env+ from +client+ and the partial body +body+
         | 
| 799 498 | 
             
                # plus a potential Content-Length value +cl+, finish reading
         | 
| @@ -801,6 +500,7 @@ module Puma | |
| 801 500 | 
             
                #
         | 
| 802 501 | 
             
                # If the body is larger than MAX_BODY, a Tempfile object is used
         | 
| 803 502 | 
             
                # for the body, otherwise a StringIO is used.
         | 
| 503 | 
            +
                # @deprecated 6.0.0
         | 
| 804 504 | 
             
                #
         | 
| 805 505 | 
             
                def read_body(env, client, body, cl)
         | 
| 806 506 | 
             
                  content_length = cl.to_i
         | 
| @@ -833,7 +533,7 @@ module Puma | |
| 833 533 |  | 
| 834 534 | 
             
                  remain -= stream.write(chunk)
         | 
| 835 535 |  | 
| 836 | 
            -
                  #  | 
| 536 | 
            +
                  # Read the rest of the chunks
         | 
| 837 537 | 
             
                  while remain > 0
         | 
| 838 538 | 
             
                    chunk = client.readpartial(CHUNK_SIZE)
         | 
| 839 539 | 
             
                    unless chunk
         | 
| @@ -848,6 +548,25 @@ module Puma | |
| 848 548 |  | 
| 849 549 | 
             
                  return stream
         | 
| 850 550 | 
             
                end
         | 
| 551 | 
            +
                # :nocov:
         | 
| 552 | 
            +
             | 
| 553 | 
            +
                # Handle various error types thrown by Client I/O operations.
         | 
| 554 | 
            +
                def client_error(e, client)
         | 
| 555 | 
            +
                  # Swallow, do not log
         | 
| 556 | 
            +
                  return if [ConnectionError, EOFError].include?(e.class)
         | 
| 557 | 
            +
             | 
| 558 | 
            +
                  lowlevel_error(e, client.env)
         | 
| 559 | 
            +
                  case e
         | 
| 560 | 
            +
                  when MiniSSL::SSLError
         | 
| 561 | 
            +
                    @events.ssl_error e, client.io
         | 
| 562 | 
            +
                  when HttpParserError
         | 
| 563 | 
            +
                    client.write_error(400)
         | 
| 564 | 
            +
                    @events.parse_error e, client
         | 
| 565 | 
            +
                  else
         | 
| 566 | 
            +
                    client.write_error(500)
         | 
| 567 | 
            +
                    @events.unknown_error e, nil, "Read"
         | 
| 568 | 
            +
                  end
         | 
| 569 | 
            +
                end
         | 
| 851 570 |  | 
| 852 571 | 
             
                # A fallback rack response if +@app+ raises as exception.
         | 
| 853 572 | 
             
                #
         | 
| @@ -915,7 +634,7 @@ module Puma | |
| 915 634 |  | 
| 916 635 | 
             
                  if @thread_pool
         | 
| 917 636 | 
             
                    if timeout = @options[:force_shutdown_after]
         | 
| 918 | 
            -
                      @thread_pool.shutdown timeout. | 
| 637 | 
            +
                      @thread_pool.shutdown timeout.to_f
         | 
| 919 638 | 
             
                    else
         | 
| 920 639 | 
             
                      @thread_pool.shutdown
         | 
| 921 640 | 
             
                    end
         | 
| @@ -923,19 +642,16 @@ module Puma | |
| 923 642 | 
             
                end
         | 
| 924 643 |  | 
| 925 644 | 
             
                def notify_safely(message)
         | 
| 926 | 
            -
                  @ | 
| 927 | 
            -
             | 
| 928 | 
            -
             | 
| 929 | 
            -
                   | 
| 930 | 
            -
             | 
| 645 | 
            +
                  @notify << message
         | 
| 646 | 
            +
                rescue IOError, NoMethodError, Errno::EPIPE
         | 
| 647 | 
            +
                  # The server, in another thread, is shutting down
         | 
| 648 | 
            +
                  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
         | 
| 649 | 
            +
                rescue RuntimeError => e
         | 
| 650 | 
            +
                  # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
         | 
| 651 | 
            +
                  if e.message.include?('IOError')
         | 
| 931 652 | 
             
                    Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
         | 
| 932 | 
            -
                   | 
| 933 | 
            -
                     | 
| 934 | 
            -
                    if e.message.include?('IOError')
         | 
| 935 | 
            -
                      Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
         | 
| 936 | 
            -
                    else
         | 
| 937 | 
            -
                      raise e
         | 
| 938 | 
            -
                    end
         | 
| 653 | 
            +
                  else
         | 
| 654 | 
            +
                    raise e
         | 
| 939 655 | 
             
                  end
         | 
| 940 656 | 
             
                end
         | 
| 941 657 | 
             
                private :notify_safely
         | 
| @@ -958,48 +674,17 @@ module Puma | |
| 958 674 | 
             
                  @thread.join if @thread && sync
         | 
| 959 675 | 
             
                end
         | 
| 960 676 |  | 
| 961 | 
            -
                def fast_write(io, str)
         | 
| 962 | 
            -
                  n = 0
         | 
| 963 | 
            -
                  while true
         | 
| 964 | 
            -
                    begin
         | 
| 965 | 
            -
                      n = io.syswrite str
         | 
| 966 | 
            -
                    rescue Errno::EAGAIN, Errno::EWOULDBLOCK
         | 
| 967 | 
            -
                      if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
         | 
| 968 | 
            -
                        raise ConnectionError, "Socket timeout writing data"
         | 
| 969 | 
            -
                      end
         | 
| 970 | 
            -
             | 
| 971 | 
            -
                      retry
         | 
| 972 | 
            -
                    rescue  Errno::EPIPE, SystemCallError, IOError
         | 
| 973 | 
            -
                      raise ConnectionError, "Socket timeout writing data"
         | 
| 974 | 
            -
                    end
         | 
| 975 | 
            -
             | 
| 976 | 
            -
                    return if n == str.bytesize
         | 
| 977 | 
            -
                    str = str.byteslice(n..-1)
         | 
| 978 | 
            -
                  end
         | 
| 979 | 
            -
                end
         | 
| 980 | 
            -
                private :fast_write
         | 
| 981 | 
            -
             | 
| 982 | 
            -
                ThreadLocalKey = :puma_server
         | 
| 983 | 
            -
             | 
| 984 | 
            -
                def self.current
         | 
| 985 | 
            -
                  Thread.current[ThreadLocalKey]
         | 
| 986 | 
            -
                end
         | 
| 987 | 
            -
             | 
| 988 677 | 
             
                def shutting_down?
         | 
| 989 678 | 
             
                  @status == :stop || @status == :restart
         | 
| 990 679 | 
             
                end
         | 
| 991 680 |  | 
| 992 | 
            -
                def possible_header_injection?(header_value)
         | 
| 993 | 
            -
                  HTTP_INJECTION_REGEX =~ header_value.to_s
         | 
| 994 | 
            -
                end
         | 
| 995 | 
            -
                private :possible_header_injection?
         | 
| 996 | 
            -
             | 
| 997 681 | 
             
                # List of methods invoked by #stats.
         | 
| 998 682 | 
             
                # @version 5.0.0
         | 
| 999 683 | 
             
                STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
         | 
| 1000 684 |  | 
| 1001 685 | 
             
                # Returns a hash of stats about the running server for reporting purposes.
         | 
| 1002 686 | 
             
                # @version 5.0.0
         | 
| 687 | 
            +
                # @!attribute [r] stats
         | 
| 1003 688 | 
             
                def stats
         | 
| 1004 689 | 
             
                  STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
         | 
| 1005 690 | 
             
                end
         |