puma 5.3.2 → 5.6.4
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 +148 -8
- data/LICENSE +0 -0
- data/README.md +47 -6
- data/bin/puma-wild +0 -0
- data/docs/architecture.md +49 -16
- data/docs/compile_options.md +4 -2
- data/docs/deployment.md +53 -67
- data/docs/fork_worker.md +0 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/jungle/README.md +0 -0
- data/docs/jungle/rc.d/README.md +0 -0
- data/docs/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +0 -0
- data/docs/nginx.md +0 -0
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +2 -3
- data/docs/restart.md +6 -6
- data/docs/signals.md +11 -10
- data/docs/stats.md +8 -8
- data/docs/systemd.md +64 -67
- data/ext/puma_http11/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/ext_help.h +0 -0
- data/ext/puma_http11/extconf.rb +28 -5
- data/ext/puma_http11/http11_parser.c +23 -10
- data/ext/puma_http11/http11_parser.h +0 -0
- data/ext/puma_http11/http11_parser.java.rl +0 -0
- data/ext/puma_http11/http11_parser.rl +0 -0
- data/ext/puma_http11/http11_parser_common.rl +1 -1
- data/ext/puma_http11/mini_ssl.c +69 -9
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +49 -47
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +28 -43
- data/ext/puma_http11/puma_http11.c +1 -1
- data/lib/puma/app/status.rb +4 -4
- data/lib/puma/binder.rb +50 -5
- data/lib/puma/cli.rb +14 -4
- data/lib/puma/client.rb +104 -20
- data/lib/puma/cluster/worker.rb +8 -18
- data/lib/puma/cluster/worker_handle.rb +4 -0
- data/lib/puma/cluster.rb +30 -24
- data/lib/puma/commonlogger.rb +0 -0
- data/lib/puma/configuration.rb +4 -1
- data/lib/puma/const.rb +9 -8
- data/lib/puma/control_cli.rb +1 -1
- data/lib/puma/detect.rb +8 -2
- data/lib/puma/dsl.rb +105 -11
- data/lib/puma/error_logger.rb +0 -0
- data/lib/puma/events.rb +0 -0
- data/lib/puma/io_buffer.rb +0 -0
- data/lib/puma/jruby_restart.rb +0 -0
- data/lib/puma/{json.rb → json_serialization.rb} +1 -1
- data/lib/puma/launcher.rb +4 -1
- data/lib/puma/minissl/context_builder.rb +8 -6
- data/lib/puma/minissl.rb +24 -23
- data/lib/puma/null_io.rb +0 -0
- data/lib/puma/plugin/tmp_restart.rb +0 -0
- data/lib/puma/plugin.rb +2 -2
- data/lib/puma/queue_close.rb +0 -0
- data/lib/puma/rack/builder.rb +1 -1
- data/lib/puma/rack/urlmap.rb +0 -0
- data/lib/puma/rack_default.rb +0 -0
- data/lib/puma/reactor.rb +0 -0
- data/lib/puma/request.rb +14 -9
- data/lib/puma/runner.rb +22 -8
- data/lib/puma/server.rb +35 -29
- data/lib/puma/single.rb +0 -0
- data/lib/puma/state_file.rb +41 -7
- data/lib/puma/systemd.rb +0 -0
- data/lib/puma/thread_pool.rb +7 -5
- data/lib/puma/util.rb +8 -1
- data/lib/puma.rb +2 -2
- data/lib/rack/handler/puma.rb +0 -0
- data/tools/Dockerfile +1 -1
- data/tools/trickletest.rb +0 -0
- metadata +7 -7
    
        data/lib/puma/cli.rb
    CHANGED
    
    | @@ -11,16 +11,17 @@ require 'puma/events' | |
| 11 11 |  | 
| 12 12 | 
             
            module Puma
         | 
| 13 13 | 
             
              class << self
         | 
| 14 | 
            -
                # The CLI exports  | 
| 15 | 
            -
                # apps to pick it up. An app  | 
| 16 | 
            -
                #  | 
| 17 | 
            -
                #  | 
| 14 | 
            +
                # The CLI exports a Puma::Configuration instance here to allow
         | 
| 15 | 
            +
                # apps to pick it up. An app must load this object conditionally
         | 
| 16 | 
            +
                # because it is not set if the app is launched via any mechanism
         | 
| 17 | 
            +
                # other than the CLI class.
         | 
| 18 18 | 
             
                attr_accessor :cli_config
         | 
| 19 19 | 
             
              end
         | 
| 20 20 |  | 
| 21 21 | 
             
              # Handles invoke a Puma::Server in a command line style.
         | 
| 22 22 | 
             
              #
         | 
| 23 23 | 
             
              class CLI
         | 
| 24 | 
            +
                # @deprecated 6.0.0
         | 
| 24 25 | 
             
                KEYS_NOT_TO_PERSIST_IN_STATE = Launcher::KEYS_NOT_TO_PERSIST_IN_STATE
         | 
| 25 26 |  | 
| 26 27 | 
             
                # Create a new CLI object using +argv+ as the command line
         | 
| @@ -112,6 +113,11 @@ module Puma | |
| 112 113 | 
             
                        file_config.load arg
         | 
| 113 114 | 
             
                      end
         | 
| 114 115 |  | 
| 116 | 
            +
                      # Identical to supplying --config "-", but more semantic
         | 
| 117 | 
            +
                      o.on "--no-config", "Prevent Puma from searching for a config file" do |arg|
         | 
| 118 | 
            +
                        file_config.load "-"
         | 
| 119 | 
            +
                      end
         | 
| 120 | 
            +
             | 
| 115 121 | 
             
                      o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
         | 
| 116 122 | 
             
                        configure_control_url(arg)
         | 
| 117 123 | 
             
                      end
         | 
| @@ -179,6 +185,10 @@ module Puma | |
| 179 185 | 
             
                        user_config.restart_command cmd
         | 
| 180 186 | 
             
                      end
         | 
| 181 187 |  | 
| 188 | 
            +
                      o.on "-s", "--silent", "Do not log prompt messages other than errors" do
         | 
| 189 | 
            +
                        @events = Events.new NullIO.new, $stderr
         | 
| 190 | 
            +
                      end
         | 
| 191 | 
            +
             | 
| 182 192 | 
             
                      o.on "-S", "--state PATH", "Where to store the state details" do |arg|
         | 
| 183 193 | 
             
                        user_config.state_path arg
         | 
| 184 194 | 
             
                      end
         | 
    
        data/lib/puma/client.rb
    CHANGED
    
    | @@ -23,6 +23,8 @@ module Puma | |
| 23 23 |  | 
| 24 24 | 
             
              class ConnectionError < RuntimeError; end
         | 
| 25 25 |  | 
| 26 | 
            +
              class HttpParserError501 < IOError; end
         | 
| 27 | 
            +
             | 
| 26 28 | 
             
              # An instance of this class represents a unique request from a client.
         | 
| 27 29 | 
             
              # For example, this could be a web request from a browser or from CURL.
         | 
| 28 30 | 
             
              #
         | 
| @@ -35,7 +37,21 @@ module Puma | |
| 35 37 | 
             
              # Instances of this class are responsible for knowing if
         | 
| 36 38 | 
             
              # the header and body are fully buffered via the `try_to_finish` method.
         | 
| 37 39 | 
             
              # They can be used to "time out" a response via the `timeout_at` reader.
         | 
| 40 | 
            +
              #
         | 
| 38 41 | 
             
              class Client
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                # this tests all values but the last, which must be chunked
         | 
| 44 | 
            +
                ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                # chunked body validation
         | 
| 47 | 
            +
                CHUNK_SIZE_INVALID = /[^\h]/.freeze
         | 
| 48 | 
            +
                CHUNK_VALID_ENDING = "\r\n".freeze
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                # Content-Length header value validation
         | 
| 51 | 
            +
                CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                TE_ERR_MSG = 'Invalid Transfer-Encoding'
         | 
| 54 | 
            +
             | 
| 39 55 | 
             
                # The object used for a request with no body. All requests with
         | 
| 40 56 | 
             
                # no body share this one object since it has no state.
         | 
| 41 57 | 
             
                EmptyBody = NullIO.new
         | 
| @@ -56,6 +72,7 @@ module Puma | |
| 56 72 | 
             
                  @parser = HttpParser.new
         | 
| 57 73 | 
             
                  @parsed_bytes = 0
         | 
| 58 74 | 
             
                  @read_header = true
         | 
| 75 | 
            +
                  @read_proxy = false
         | 
| 59 76 | 
             
                  @ready = false
         | 
| 60 77 |  | 
| 61 78 | 
             
                  @body = nil
         | 
| @@ -71,6 +88,7 @@ module Puma | |
| 71 88 | 
             
                  @peerip = nil
         | 
| 72 89 | 
             
                  @listener = nil
         | 
| 73 90 | 
             
                  @remote_addr_header = nil
         | 
| 91 | 
            +
                  @expect_proxy_proto = false
         | 
| 74 92 |  | 
| 75 93 | 
             
                  @body_remain = 0
         | 
| 76 94 |  | 
| @@ -106,7 +124,7 @@ module Puma | |
| 106 124 |  | 
| 107 125 | 
             
                # @!attribute [r] in_data_phase
         | 
| 108 126 | 
             
                def in_data_phase
         | 
| 109 | 
            -
                   | 
| 127 | 
            +
                  !(@read_header || @read_proxy)
         | 
| 110 128 | 
             
                end
         | 
| 111 129 |  | 
| 112 130 | 
             
                def set_timeout(val)
         | 
| @@ -121,6 +139,7 @@ module Puma | |
| 121 139 | 
             
                def reset(fast_check=true)
         | 
| 122 140 | 
             
                  @parser.reset
         | 
| 123 141 | 
             
                  @read_header = true
         | 
| 142 | 
            +
                  @read_proxy = !!@expect_proxy_proto
         | 
| 124 143 | 
             
                  @env = @proto_env.dup
         | 
| 125 144 | 
             
                  @body = nil
         | 
| 126 145 | 
             
                  @tempfile = nil
         | 
| @@ -131,6 +150,8 @@ module Puma | |
| 131 150 | 
             
                  @in_last_chunk = false
         | 
| 132 151 |  | 
| 133 152 | 
             
                  if @buffer
         | 
| 153 | 
            +
                    return false unless try_to_parse_proxy_protocol
         | 
| 154 | 
            +
             | 
| 134 155 | 
             
                    @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
         | 
| 135 156 |  | 
| 136 157 | 
             
                    if @parser.finished?
         | 
| @@ -143,8 +164,7 @@ module Puma | |
| 143 164 | 
             
                    return false
         | 
| 144 165 | 
             
                  else
         | 
| 145 166 | 
             
                    begin
         | 
| 146 | 
            -
                      if fast_check &&
         | 
| 147 | 
            -
                          IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
         | 
| 167 | 
            +
                      if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
         | 
| 148 168 | 
             
                        return try_to_finish
         | 
| 149 169 | 
             
                      end
         | 
| 150 170 | 
             
                    rescue IOError
         | 
| @@ -157,13 +177,37 @@ module Puma | |
| 157 177 | 
             
                def close
         | 
| 158 178 | 
             
                  begin
         | 
| 159 179 | 
             
                    @io.close
         | 
| 160 | 
            -
                  rescue IOError
         | 
| 161 | 
            -
                     | 
| 180 | 
            +
                  rescue IOError, Errno::EBADF
         | 
| 181 | 
            +
                    Puma::Util.purge_interrupt_queue
         | 
| 162 182 | 
             
                  end
         | 
| 163 183 | 
             
                end
         | 
| 164 184 |  | 
| 185 | 
            +
                # If necessary, read the PROXY protocol from the buffer. Returns
         | 
| 186 | 
            +
                # false if more data is needed.
         | 
| 187 | 
            +
                def try_to_parse_proxy_protocol
         | 
| 188 | 
            +
                  if @read_proxy
         | 
| 189 | 
            +
                    if @expect_proxy_proto == :v1
         | 
| 190 | 
            +
                      if @buffer.include? "\r\n"
         | 
| 191 | 
            +
                        if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
         | 
| 192 | 
            +
                          if md[1]
         | 
| 193 | 
            +
                            @peerip = md[1].split(" ")[0]
         | 
| 194 | 
            +
                          end
         | 
| 195 | 
            +
                          @buffer = md.post_match
         | 
| 196 | 
            +
                        end
         | 
| 197 | 
            +
                        # if the buffer has a \r\n but doesn't have a PROXY protocol
         | 
| 198 | 
            +
                        # request, this is just HTTP from a non-PROXY client; move on
         | 
| 199 | 
            +
                        @read_proxy = false
         | 
| 200 | 
            +
                        return @buffer.size > 0
         | 
| 201 | 
            +
                      else
         | 
| 202 | 
            +
                        return false
         | 
| 203 | 
            +
                      end
         | 
| 204 | 
            +
                    end
         | 
| 205 | 
            +
                  end
         | 
| 206 | 
            +
                  true
         | 
| 207 | 
            +
                end
         | 
| 208 | 
            +
             | 
| 165 209 | 
             
                def try_to_finish
         | 
| 166 | 
            -
                  return read_body  | 
| 210 | 
            +
                  return read_body if in_data_phase
         | 
| 167 211 |  | 
| 168 212 | 
             
                  begin
         | 
| 169 213 | 
             
                    data = @io.read_nonblock(CHUNK_SIZE)
         | 
| @@ -188,6 +232,8 @@ module Puma | |
| 188 232 | 
             
                    @buffer = data
         | 
| 189 233 | 
             
                  end
         | 
| 190 234 |  | 
| 235 | 
            +
                  return false unless try_to_parse_proxy_protocol
         | 
| 236 | 
            +
             | 
| 191 237 | 
             
                  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
         | 
| 192 238 |  | 
| 193 239 | 
             
                  if @parser.finished?
         | 
| @@ -202,13 +248,13 @@ module Puma | |
| 202 248 |  | 
| 203 249 | 
             
                def eagerly_finish
         | 
| 204 250 | 
             
                  return true if @ready
         | 
| 205 | 
            -
                  return false unless  | 
| 251 | 
            +
                  return false unless @to_io.wait_readable(0)
         | 
| 206 252 | 
             
                  try_to_finish
         | 
| 207 253 | 
             
                end
         | 
| 208 254 |  | 
| 209 255 | 
             
                def finish(timeout)
         | 
| 210 256 | 
             
                  return if @ready
         | 
| 211 | 
            -
                   | 
| 257 | 
            +
                  @to_io.wait_readable(timeout) || timeout! until try_to_finish
         | 
| 212 258 | 
             
                end
         | 
| 213 259 |  | 
| 214 260 | 
             
                def timeout!
         | 
| @@ -244,6 +290,17 @@ module Puma | |
| 244 290 | 
             
                  @parsed_bytes == 0
         | 
| 245 291 | 
             
                end
         | 
| 246 292 |  | 
| 293 | 
            +
                def expect_proxy_proto=(val)
         | 
| 294 | 
            +
                  if val
         | 
| 295 | 
            +
                    if @read_header
         | 
| 296 | 
            +
                      @read_proxy = true
         | 
| 297 | 
            +
                    end
         | 
| 298 | 
            +
                  else
         | 
| 299 | 
            +
                    @read_proxy = false
         | 
| 300 | 
            +
                  end
         | 
| 301 | 
            +
                  @expect_proxy_proto = val
         | 
| 302 | 
            +
                end
         | 
| 303 | 
            +
             | 
| 247 304 | 
             
                private
         | 
| 248 305 |  | 
| 249 306 | 
             
                def setup_body
         | 
| @@ -261,16 +318,27 @@ module Puma | |
| 261 318 | 
             
                  body = @parser.body
         | 
| 262 319 |  | 
| 263 320 | 
             
                  te = @env[TRANSFER_ENCODING2]
         | 
| 264 | 
            -
             | 
| 265 321 | 
             
                  if te
         | 
| 266 | 
            -
                     | 
| 267 | 
            -
             | 
| 268 | 
            -
             | 
| 269 | 
            -
             | 
| 270 | 
            -
             | 
| 322 | 
            +
                    te_lwr = te.downcase
         | 
| 323 | 
            +
                    if te.include? ','
         | 
| 324 | 
            +
                      te_ary = te_lwr.split ','
         | 
| 325 | 
            +
                      te_count = te_ary.count CHUNKED
         | 
| 326 | 
            +
                      te_valid = te_ary[0..-2].all? { |e| ALLOWED_TRANSFER_ENCODING.include? e }
         | 
| 327 | 
            +
                      if te_ary.last == CHUNKED && te_count == 1 && te_valid
         | 
| 328 | 
            +
                        @env.delete TRANSFER_ENCODING2
         | 
| 329 | 
            +
                        return setup_chunked_body body
         | 
| 330 | 
            +
                      elsif te_count >= 1
         | 
| 331 | 
            +
                        raise HttpParserError   , "#{TE_ERR_MSG}, multiple chunked: '#{te}'"
         | 
| 332 | 
            +
                      elsif !te_valid
         | 
| 333 | 
            +
                        raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
         | 
| 271 334 | 
             
                      end
         | 
| 272 | 
            -
                    elsif  | 
| 273 | 
            -
                       | 
| 335 | 
            +
                    elsif te_lwr == CHUNKED
         | 
| 336 | 
            +
                      @env.delete TRANSFER_ENCODING2
         | 
| 337 | 
            +
                      return setup_chunked_body body
         | 
| 338 | 
            +
                    elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
         | 
| 339 | 
            +
                      raise HttpParserError     , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
         | 
| 340 | 
            +
                    else
         | 
| 341 | 
            +
                      raise HttpParserError501  , "#{TE_ERR_MSG}, unknown value: '#{te}'"
         | 
| 274 342 | 
             
                    end
         | 
| 275 343 | 
             
                  end
         | 
| 276 344 |  | 
| @@ -278,7 +346,12 @@ module Puma | |
| 278 346 |  | 
| 279 347 | 
             
                  cl = @env[CONTENT_LENGTH]
         | 
| 280 348 |  | 
| 281 | 
            -
                   | 
| 349 | 
            +
                  if cl
         | 
| 350 | 
            +
                    # cannot contain characters that are not \d
         | 
| 351 | 
            +
                    if cl =~ CONTENT_LENGTH_VALUE_INVALID
         | 
| 352 | 
            +
                      raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
         | 
| 353 | 
            +
                    end
         | 
| 354 | 
            +
                  else
         | 
| 282 355 | 
             
                    @buffer = body.empty? ? nil : body
         | 
| 283 356 | 
             
                    @body = EmptyBody
         | 
| 284 357 | 
             
                    set_ready
         | 
| @@ -309,7 +382,7 @@ module Puma | |
| 309 382 |  | 
| 310 383 | 
             
                  @body_remain = remain
         | 
| 311 384 |  | 
| 312 | 
            -
                   | 
| 385 | 
            +
                  false
         | 
| 313 386 | 
             
                end
         | 
| 314 387 |  | 
| 315 388 | 
             
                def read_body
         | 
| @@ -437,7 +510,13 @@ module Puma | |
| 437 510 | 
             
                  while !io.eof?
         | 
| 438 511 | 
             
                    line = io.gets
         | 
| 439 512 | 
             
                    if line.end_with?("\r\n")
         | 
| 440 | 
            -
                       | 
| 513 | 
            +
                      # Puma doesn't process chunk extensions, but should parse if they're
         | 
| 514 | 
            +
                      # present, which is the reason for the semicolon regex
         | 
| 515 | 
            +
                      chunk_hex = line.strip[/\A[^;]+/]
         | 
| 516 | 
            +
                      if chunk_hex =~ CHUNK_SIZE_INVALID
         | 
| 517 | 
            +
                        raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
         | 
| 518 | 
            +
                      end
         | 
| 519 | 
            +
                      len = chunk_hex.to_i(16)
         | 
| 441 520 | 
             
                      if len == 0
         | 
| 442 521 | 
             
                        @in_last_chunk = true
         | 
| 443 522 | 
             
                        @body.rewind
         | 
| @@ -468,7 +547,12 @@ module Puma | |
| 468 547 |  | 
| 469 548 | 
             
                      case
         | 
| 470 549 | 
             
                      when got == len
         | 
| 471 | 
            -
                         | 
| 550 | 
            +
                        # proper chunked segment must end with "\r\n"
         | 
| 551 | 
            +
                        if part.end_with? CHUNK_VALID_ENDING
         | 
| 552 | 
            +
                          write_chunk(part[0..-3]) # to skip the ending \r\n
         | 
| 553 | 
            +
                        else
         | 
| 554 | 
            +
                          raise HttpParserError, "Chunk size mismatch"
         | 
| 555 | 
            +
                        end
         | 
| 472 556 | 
             
                      when got <= len - 2
         | 
| 473 557 | 
             
                        write_chunk(part)
         | 
| 474 558 | 
             
                        @partial_part_left = len - part.size
         | 
    
        data/lib/puma/cluster/worker.rb
    CHANGED
    
    | @@ -33,9 +33,9 @@ module Puma | |
| 33 33 | 
             
                    Signal.trap "SIGINT", "IGNORE"
         | 
| 34 34 | 
             
                    Signal.trap "SIGCHLD", "DEFAULT"
         | 
| 35 35 |  | 
| 36 | 
            -
             | 
| 37 | 
            -
                      Puma.set_thread_name " | 
| 38 | 
            -
                       | 
| 36 | 
            +
                    Thread.new do
         | 
| 37 | 
            +
                      Puma.set_thread_name "wrkr check"
         | 
| 38 | 
            +
                      @check_pipe.wait_readable
         | 
| 39 39 | 
             
                      log "! Detected parent died, dying"
         | 
| 40 40 | 
             
                      exit! 1
         | 
| 41 41 | 
             
                    end
         | 
| @@ -76,7 +76,7 @@ module Puma | |
| 76 76 | 
             
                      end
         | 
| 77 77 |  | 
| 78 78 | 
             
                      Thread.new do
         | 
| 79 | 
            -
                        Puma.set_thread_name " | 
| 79 | 
            +
                        Puma.set_thread_name "wrkr fork"
         | 
| 80 80 | 
             
                        while (idx = @fork_pipe.gets)
         | 
| 81 81 | 
             
                          idx = idx.to_i
         | 
| 82 82 | 
             
                          if idx == -1 # stop server
         | 
| @@ -106,7 +106,7 @@ module Puma | |
| 106 106 | 
             
                    begin
         | 
| 107 107 | 
             
                      @worker_write << "b#{Process.pid}:#{index}\n"
         | 
| 108 108 | 
             
                    rescue SystemCallError, IOError
         | 
| 109 | 
            -
                       | 
| 109 | 
            +
                      Puma::Util.purge_interrupt_queue
         | 
| 110 110 | 
             
                      STDERR.puts "Master seems to have exited, exiting."
         | 
| 111 111 | 
             
                      return
         | 
| 112 112 | 
             
                    end
         | 
| @@ -114,7 +114,7 @@ module Puma | |
| 114 114 | 
             
                    while restart_server.pop
         | 
| 115 115 | 
             
                      server_thread = server.run
         | 
| 116 116 | 
             
                      stat_thread ||= Thread.new(@worker_write) do |io|
         | 
| 117 | 
            -
                        Puma.set_thread_name "stat  | 
| 117 | 
            +
                        Puma.set_thread_name "stat pld"
         | 
| 118 118 | 
             
                        base_payload = "p#{Process.pid}"
         | 
| 119 119 |  | 
| 120 120 | 
             
                        while true
         | 
| @@ -127,10 +127,10 @@ module Puma | |
| 127 127 | 
             
                            payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
         | 
| 128 128 | 
             
                            io << payload
         | 
| 129 129 | 
             
                          rescue IOError
         | 
| 130 | 
            -
                             | 
| 130 | 
            +
                            Puma::Util.purge_interrupt_queue
         | 
| 131 131 | 
             
                            break
         | 
| 132 132 | 
             
                          end
         | 
| 133 | 
            -
                          sleep  | 
| 133 | 
            +
                          sleep @options[:worker_check_interval]
         | 
| 134 134 | 
             
                        end
         | 
| 135 135 | 
             
                      end
         | 
| 136 136 | 
             
                      server_thread.join
         | 
| @@ -168,16 +168,6 @@ module Puma | |
| 168 168 | 
             
                    @launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
         | 
| 169 169 | 
             
                    pid
         | 
| 170 170 | 
             
                  end
         | 
| 171 | 
            -
             | 
| 172 | 
            -
                  def wakeup!
         | 
| 173 | 
            -
                    return unless @wakeup
         | 
| 174 | 
            -
             | 
| 175 | 
            -
                    begin
         | 
| 176 | 
            -
                      @wakeup.write "!" unless @wakeup.closed?
         | 
| 177 | 
            -
                    rescue SystemCallError, IOError
         | 
| 178 | 
            -
                      Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
         | 
| 179 | 
            -
                    end
         | 
| 180 | 
            -
                  end
         | 
| 181 171 | 
             
                end
         | 
| 182 172 | 
             
              end
         | 
| 183 173 | 
             
            end
         | 
    
        data/lib/puma/cluster.rb
    CHANGED
    
    | @@ -108,24 +108,42 @@ module Puma | |
| 108 108 | 
             
                def cull_workers
         | 
| 109 109 | 
             
                  diff = @workers.size - @options[:workers]
         | 
| 110 110 | 
             
                  return if diff < 1
         | 
| 111 | 
            +
                  debug "Culling #{diff} workers"
         | 
| 111 112 |  | 
| 112 | 
            -
                   | 
| 113 | 
            +
                  workers = workers_to_cull(diff)
         | 
| 114 | 
            +
                  debug "Workers to cull: #{workers.inspect}"
         | 
| 113 115 |  | 
| 114 | 
            -
                   | 
| 115 | 
            -
                  debug "Workers to cull: #{workers_to_cull.inspect}"
         | 
| 116 | 
            -
             | 
| 117 | 
            -
                  workers_to_cull.each do |worker|
         | 
| 116 | 
            +
                  workers.each do |worker|
         | 
| 118 117 | 
             
                    log "- Worker #{worker.index} (PID: #{worker.pid}) terminating"
         | 
| 119 118 | 
             
                    worker.term
         | 
| 120 119 | 
             
                  end
         | 
| 121 120 | 
             
                end
         | 
| 122 121 |  | 
| 122 | 
            +
                def workers_to_cull(diff)
         | 
| 123 | 
            +
                  workers = @workers.sort_by(&:started_at)
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                  # In fork_worker mode, worker 0 acts as our master process.
         | 
| 126 | 
            +
                  # We should avoid culling it to preserve copy-on-write memory gains.
         | 
| 127 | 
            +
                  workers.reject! { |w| w.index == 0 } if @options[:fork_worker]
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                  workers[cull_start_index(diff), diff]
         | 
| 130 | 
            +
                end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                def cull_start_index(diff)
         | 
| 133 | 
            +
                  case @options[:worker_culling_strategy]
         | 
| 134 | 
            +
                  when :oldest
         | 
| 135 | 
            +
                    0
         | 
| 136 | 
            +
                  else # :youngest
         | 
| 137 | 
            +
                    -diff
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
                end
         | 
| 140 | 
            +
             | 
| 123 141 | 
             
                # @!attribute [r] next_worker_index
         | 
| 124 142 | 
             
                def next_worker_index
         | 
| 125 | 
            -
                   | 
| 126 | 
            -
                   | 
| 127 | 
            -
                   | 
| 128 | 
            -
                   | 
| 143 | 
            +
                  occupied_positions = @workers.map(&:index)
         | 
| 144 | 
            +
                  idx = 0
         | 
| 145 | 
            +
                  idx += 1 until !occupied_positions.include?(idx)
         | 
| 146 | 
            +
                  idx
         | 
| 129 147 | 
             
                end
         | 
| 130 148 |  | 
| 131 149 | 
             
                def all_workers_booted?
         | 
| @@ -135,7 +153,7 @@ module Puma | |
| 135 153 | 
             
                def check_workers
         | 
| 136 154 | 
             
                  return if @next_check >= Time.now
         | 
| 137 155 |  | 
| 138 | 
            -
                  @next_check = Time.now +  | 
| 156 | 
            +
                  @next_check = Time.now + @options[:worker_check_interval]
         | 
| 139 157 |  | 
| 140 158 | 
             
                  timeout_workers
         | 
| 141 159 | 
             
                  wait_workers
         | 
| @@ -164,16 +182,6 @@ module Puma | |
| 164 182 | 
             
                  ].compact.min
         | 
| 165 183 | 
             
                end
         | 
| 166 184 |  | 
| 167 | 
            -
                def wakeup!
         | 
| 168 | 
            -
                  return unless @wakeup
         | 
| 169 | 
            -
             | 
| 170 | 
            -
                  begin
         | 
| 171 | 
            -
                    @wakeup.write "!" unless @wakeup.closed?
         | 
| 172 | 
            -
                  rescue SystemCallError, IOError
         | 
| 173 | 
            -
                    Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
         | 
| 174 | 
            -
                  end
         | 
| 175 | 
            -
                end
         | 
| 176 | 
            -
             | 
| 177 185 | 
             
                def worker(index, master)
         | 
| 178 186 | 
             
                  @workers = []
         | 
| 179 187 |  | 
| @@ -426,9 +434,7 @@ module Puma | |
| 426 434 |  | 
| 427 435 | 
             
                        check_workers
         | 
| 428 436 |  | 
| 429 | 
            -
                         | 
| 430 | 
            -
             | 
| 431 | 
            -
                        if res
         | 
| 437 | 
            +
                        if read.wait_readable([0, @next_check - Time.now].max)
         | 
| 432 438 | 
             
                          req = read.read_nonblock(1)
         | 
| 433 439 |  | 
| 434 440 | 
             
                          @next_check = Time.now if req == "!"
         | 
| @@ -452,7 +458,7 @@ module Puma | |
| 452 458 | 
             
                              workers_not_booted -= 1
         | 
| 453 459 | 
             
                            when "e"
         | 
| 454 460 | 
             
                              # external term, see worker method, Signal.trap "SIGTERM"
         | 
| 455 | 
            -
                              w. | 
| 461 | 
            +
                              w.term!
         | 
| 456 462 | 
             
                            when "t"
         | 
| 457 463 | 
             
                              w.term unless w.term?
         | 
| 458 464 | 
             
                            when "p"
         | 
    
        data/lib/puma/commonlogger.rb
    CHANGED
    
    | 
            File without changes
         | 
    
        data/lib/puma/configuration.rb
    CHANGED
    
    | @@ -11,6 +11,7 @@ module Puma | |
| 11 11 |  | 
| 12 12 | 
             
                DefaultTCPHost = "0.0.0.0"
         | 
| 13 13 | 
             
                DefaultTCPPort = 9292
         | 
| 14 | 
            +
                DefaultWorkerCheckInterval = 5
         | 
| 14 15 | 
             
                DefaultWorkerTimeout = 60
         | 
| 15 16 | 
             
                DefaultWorkerShutdownTimeout = 30
         | 
| 16 17 | 
             
              end
         | 
| @@ -195,12 +196,14 @@ module Puma | |
| 195 196 | 
             
                    :workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
         | 
| 196 197 | 
             
                    :silence_single_worker_warning => false,
         | 
| 197 198 | 
             
                    :mode => :http,
         | 
| 199 | 
            +
                    :worker_check_interval => DefaultWorkerCheckInterval,
         | 
| 198 200 | 
             
                    :worker_timeout => DefaultWorkerTimeout,
         | 
| 199 201 | 
             
                    :worker_boot_timeout => DefaultWorkerTimeout,
         | 
| 200 202 | 
             
                    :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
         | 
| 203 | 
            +
                    :worker_culling_strategy => :youngest,
         | 
| 201 204 | 
             
                    :remote_address => :socket,
         | 
| 202 205 | 
             
                    :tag => method(:infer_tag),
         | 
| 203 | 
            -
                    :environment => -> { ENV['RACK_ENV'] || ENV['RAILS_ENV'] ||  | 
| 206 | 
            +
                    :environment => -> { ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development' },
         | 
| 204 207 | 
             
                    :rackup => DefaultRackup,
         | 
| 205 208 | 
             
                    :logger => STDOUT,
         | 
| 206 209 | 
             
                    :persistent_timeout => Const::PERSISTENT_TIMEOUT,
         | 
    
        data/lib/puma/const.rb
    CHANGED
    
    | @@ -76,7 +76,7 @@ module Puma | |
| 76 76 | 
             
                508 => 'Loop Detected',
         | 
| 77 77 | 
             
                510 => 'Not Extended',
         | 
| 78 78 | 
             
                511 => 'Network Authentication Required'
         | 
| 79 | 
            -
              }
         | 
| 79 | 
            +
              }.freeze
         | 
| 80 80 |  | 
| 81 81 | 
             
              # For some HTTP status codes the client only expects headers.
         | 
| 82 82 | 
             
              #
         | 
| @@ -85,7 +85,7 @@ module Puma | |
| 85 85 | 
             
                204 => true,
         | 
| 86 86 | 
             
                205 => true,
         | 
| 87 87 | 
             
                304 => true
         | 
| 88 | 
            -
              }
         | 
| 88 | 
            +
              }.freeze
         | 
| 89 89 |  | 
| 90 90 | 
             
              # Frequently used constants when constructing requests or responses.  Many times
         | 
| 91 91 | 
             
              # the constant just refers to a string with the same contents.  Using these constants
         | 
| @@ -100,8 +100,8 @@ module Puma | |
| 100 100 | 
             
              # too taxing on performance.
         | 
| 101 101 | 
             
              module Const
         | 
| 102 102 |  | 
| 103 | 
            -
                PUMA_VERSION = VERSION = "5. | 
| 104 | 
            -
                CODE_NAME = " | 
| 103 | 
            +
                PUMA_VERSION = VERSION = "5.6.4".freeze
         | 
| 104 | 
            +
                CODE_NAME = "Birdie's Version".freeze
         | 
| 105 105 |  | 
| 106 106 | 
             
                PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
         | 
| 107 107 |  | 
| @@ -145,9 +145,11 @@ module Puma | |
| 145 145 | 
             
                  408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
         | 
| 146 146 | 
             
                  # Indicate that there was an internal error, obviously.
         | 
| 147 147 | 
             
                  500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
         | 
| 148 | 
            +
                  # Incorrect or invalid header value
         | 
| 149 | 
            +
                  501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze,
         | 
| 148 150 | 
             
                  # A common header for indicating the server is too busy.  Not used yet.
         | 
| 149 151 | 
             
                  503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
         | 
| 150 | 
            -
                }
         | 
| 152 | 
            +
                }.freeze
         | 
| 151 153 |  | 
| 152 154 | 
             
                # The basic max request size we'll try to read.
         | 
| 153 155 | 
             
                CHUNK_SIZE = 16 * 1024
         | 
| @@ -235,9 +237,6 @@ module Puma | |
| 235 237 |  | 
| 236 238 | 
             
                EARLY_HINTS = "rack.early_hints".freeze
         | 
| 237 239 |  | 
| 238 | 
            -
                # Minimum interval to checks worker health
         | 
| 239 | 
            -
                WORKER_CHECK_INTERVAL = 5
         | 
| 240 | 
            -
             | 
| 241 240 | 
             
                # Illegal character in the key or value of response header
         | 
| 242 241 | 
             
                DQUOTE = "\"".freeze
         | 
| 243 242 | 
             
                HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
         | 
| @@ -247,5 +246,7 @@ module Puma | |
| 247 246 |  | 
| 248 247 | 
             
                # Banned keys of response header
         | 
| 249 248 | 
             
                BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
         | 
| 250 251 | 
             
              end
         | 
| 251 252 | 
             
            end
         | 
    
        data/lib/puma/control_cli.rb
    CHANGED
    
    
    
        data/lib/puma/detect.rb
    CHANGED
    
    | @@ -10,8 +10,10 @@ module Puma | |
| 10 10 |  | 
| 11 11 | 
             
              IS_JRUBY = Object.const_defined? :JRUBY_VERSION
         | 
| 12 12 |  | 
| 13 | 
            -
               | 
| 14 | 
            -
             | 
| 13 | 
            +
              IS_OSX = RUBY_PLATFORM.include? 'darwin'
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
         | 
| 16 | 
            +
                IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
         | 
| 15 17 |  | 
| 16 18 | 
             
              # @version 5.2.0
         | 
| 17 19 | 
             
              IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
         | 
| @@ -20,6 +22,10 @@ module Puma | |
| 20 22 | 
             
                IS_JRUBY
         | 
| 21 23 | 
             
              end
         | 
| 22 24 |  | 
| 25 | 
            +
              def self.osx?
         | 
| 26 | 
            +
                IS_OSX
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 23 29 | 
             
              def self.windows?
         | 
| 24 30 | 
             
                IS_WINDOWS
         | 
| 25 31 | 
             
              end
         |