puma 5.5.2 → 6.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +336 -3
  3. data/README.md +61 -16
  4. data/bin/puma-wild +1 -1
  5. data/docs/architecture.md +4 -4
  6. data/docs/compile_options.md +34 -0
  7. data/docs/fork_worker.md +1 -3
  8. data/docs/nginx.md +1 -1
  9. data/docs/signals.md +1 -0
  10. data/docs/systemd.md +1 -2
  11. data/docs/testing_benchmarks_local_files.md +150 -0
  12. data/docs/testing_test_rackup_ci_files.md +36 -0
  13. data/ext/puma_http11/extconf.rb +28 -14
  14. data/ext/puma_http11/http11_parser.c +1 -1
  15. data/ext/puma_http11/http11_parser.h +1 -1
  16. data/ext/puma_http11/http11_parser.java.rl +2 -2
  17. data/ext/puma_http11/http11_parser.rl +2 -2
  18. data/ext/puma_http11/http11_parser_common.rl +2 -2
  19. data/ext/puma_http11/mini_ssl.c +135 -23
  20. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  21. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  22. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +188 -102
  23. data/ext/puma_http11/puma_http11.c +18 -10
  24. data/lib/puma/app/status.rb +7 -4
  25. data/lib/puma/binder.rb +62 -51
  26. data/lib/puma/cli.rb +19 -20
  27. data/lib/puma/client.rb +108 -26
  28. data/lib/puma/cluster/worker.rb +23 -16
  29. data/lib/puma/cluster/worker_handle.rb +8 -1
  30. data/lib/puma/cluster.rb +62 -41
  31. data/lib/puma/commonlogger.rb +21 -14
  32. data/lib/puma/configuration.rb +76 -55
  33. data/lib/puma/const.rb +133 -97
  34. data/lib/puma/control_cli.rb +21 -18
  35. data/lib/puma/detect.rb +12 -2
  36. data/lib/puma/dsl.rb +270 -55
  37. data/lib/puma/error_logger.rb +18 -9
  38. data/lib/puma/events.rb +6 -126
  39. data/lib/puma/io_buffer.rb +39 -4
  40. data/lib/puma/jruby_restart.rb +2 -1
  41. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  42. data/lib/puma/launcher.rb +114 -175
  43. data/lib/puma/log_writer.rb +147 -0
  44. data/lib/puma/minissl/context_builder.rb +30 -16
  45. data/lib/puma/minissl.rb +126 -17
  46. data/lib/puma/null_io.rb +5 -0
  47. data/lib/puma/plugin/systemd.rb +90 -0
  48. data/lib/puma/plugin/tmp_restart.rb +1 -1
  49. data/lib/puma/plugin.rb +1 -1
  50. data/lib/puma/rack/builder.rb +6 -6
  51. data/lib/puma/rack_default.rb +19 -4
  52. data/lib/puma/reactor.rb +19 -10
  53. data/lib/puma/request.rb +365 -161
  54. data/lib/puma/runner.rb +55 -22
  55. data/lib/puma/sd_notify.rb +149 -0
  56. data/lib/puma/server.rb +91 -94
  57. data/lib/puma/single.rb +13 -11
  58. data/lib/puma/state_file.rb +39 -7
  59. data/lib/puma/thread_pool.rb +25 -21
  60. data/lib/puma/util.rb +12 -14
  61. data/lib/puma.rb +12 -11
  62. data/lib/rack/handler/puma.rb +113 -86
  63. data/tools/Dockerfile +1 -1
  64. metadata +11 -6
  65. data/lib/puma/queue_close.rb +0 -26
  66. data/lib/puma/systemd.rb +0 -46
data/lib/puma/runner.rb CHANGED
@@ -1,23 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/server'
4
- require 'puma/const'
3
+ require_relative 'server'
4
+ require_relative 'const'
5
5
 
6
6
  module Puma
7
7
  # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
8
8
  # serve requests. This class spawns a new instance of `Puma::Server` via
9
9
  # a call to `start_server`.
10
10
  class Runner
11
- def initialize(cli, events)
12
- @launcher = cli
13
- @events = events
14
- @options = cli.options
11
+ def initialize(launcher)
12
+ @launcher = launcher
13
+ @log_writer = launcher.log_writer
14
+ @events = launcher.events
15
+ @config = launcher.config
16
+ @options = launcher.options
15
17
  @app = nil
16
18
  @control = nil
17
19
  @started_at = Time.now
18
20
  @wakeup = nil
19
21
  end
20
22
 
23
+ # Returns the hash of configuration options.
24
+ # @return [Puma::UserFileDefaultOptions]
25
+ attr_reader :options
26
+
21
27
  def wakeup!
22
28
  return unless @wakeup
23
29
 
@@ -36,27 +42,27 @@ module Puma
36
42
  end
37
43
 
38
44
  def log(str)
39
- @events.log str
45
+ @log_writer.log str
40
46
  end
41
47
 
42
48
  # @version 5.0.0
43
49
  def stop_control
44
- @control.stop(true) if @control
50
+ @control&.stop true
45
51
  end
46
52
 
47
53
  def error(str)
48
- @events.error str
54
+ @log_writer.error str
49
55
  end
50
56
 
51
57
  def debug(str)
52
- @events.log "- #{str}" if @options[:debug]
58
+ @log_writer.log "- #{str}" if @options[:debug]
53
59
  end
54
60
 
55
61
  def start_control
56
62
  str = @options[:control_url]
57
63
  return unless str
58
64
 
59
- require 'puma/app/status'
65
+ require_relative 'app/status'
60
66
 
61
67
  if token = @options[:control_auth_token]
62
68
  token = nil if token.empty? || token == 'none'
@@ -64,12 +70,14 @@ module Puma
64
70
 
65
71
  app = Puma::App::Status.new @launcher, token
66
72
 
67
- control = Puma::Server.new app, @launcher.events,
68
- { min_threads: 0, max_threads: 1, queue_requests: false }
73
+ # A Reactor is not created aand nio4r is not loaded when 'queue_requests: false'
74
+ # Use `nil` for events, no hooks in control server
75
+ control = Puma::Server.new app, nil,
76
+ { min_threads: 0, max_threads: 1, queue_requests: false, log_writer: @log_writer }
69
77
 
70
- control.binder.parse [str], self, 'Starting control server'
78
+ control.binder.parse [str], nil, 'Starting control server'
71
79
 
72
- control.run thread_name: 'control'
80
+ control.run thread_name: 'ctl'
73
81
  @control = control
74
82
  end
75
83
 
@@ -94,12 +102,13 @@ module Puma
94
102
  def output_header(mode)
95
103
  min_t = @options[:min_threads]
96
104
  max_t = @options[:max_threads]
105
+ environment = @options[:environment]
97
106
 
98
107
  log "Puma starting in #{mode} mode..."
99
108
  log "* Puma version: #{Puma::Const::PUMA_VERSION} (#{ruby_engine}) (\"#{Puma::Const::CODE_NAME}\")"
100
109
  log "* Min threads: #{min_t}"
101
110
  log "* Max threads: #{max_t}"
102
- log "* Environment: #{ENV['RACK_ENV']}"
111
+ log "* Environment: #{environment}"
103
112
 
104
113
  if mode == "cluster"
105
114
  log "* Master PID: #{Process.pid}"
@@ -140,29 +149,29 @@ module Puma
140
149
  end
141
150
 
142
151
  def load_and_bind
143
- unless @launcher.config.app_configured?
152
+ unless @config.app_configured?
144
153
  error "No application configured, nothing to run"
145
154
  exit 1
146
155
  end
147
156
 
148
157
  begin
149
- @app = @launcher.config.app
158
+ @app = @config.app
150
159
  rescue Exception => e
151
160
  log "! Unable to load application: #{e.class}: #{e.message}"
152
161
  raise e
153
162
  end
154
163
 
155
- @launcher.binder.parse @options[:binds], self
164
+ @launcher.binder.parse @options[:binds]
156
165
  end
157
166
 
158
167
  # @!attribute [r] app
159
168
  def app
160
- @app ||= @launcher.config.app
169
+ @app ||= @config.app
161
170
  end
162
171
 
163
172
  def start_server
164
- server = Puma::Server.new app, @launcher.events, @options
165
- server.inherit_binder @launcher.binder
173
+ server = Puma::Server.new(app, @events, @options)
174
+ server.inherit_binder(@launcher.binder)
166
175
  server
167
176
  end
168
177
 
@@ -172,5 +181,29 @@ module Puma
172
181
  raise "Cannot redirect #{io_name} to #{path}"
173
182
  end
174
183
  end
184
+
185
+ def utc_iso8601(val)
186
+ "#{val.utc.strftime '%FT%T'}Z"
187
+ end
188
+
189
+ def stats
190
+ {
191
+ versions: {
192
+ puma: Puma::Const::PUMA_VERSION,
193
+ ruby: {
194
+ engine: RUBY_ENGINE,
195
+ version: RUBY_VERSION,
196
+ patchlevel: RUBY_PATCHLEVEL
197
+ }
198
+ }
199
+ }
200
+ end
201
+
202
+ # this method call should always be guarded by `@log_writer.debug?`
203
+ def debug_loaded_extensions(str)
204
+ @log_writer.debug "────────────────────────────────── #{str}"
205
+ re_ext = /\.#{RbConfig::CONFIG['DLEXT']}\z/i
206
+ $LOADED_FEATURES.grep(re_ext).each { |f| @log_writer.debug(" #{f}") }
207
+ end
175
208
  end
176
209
  end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+
5
+ module Puma
6
+ # The MIT License
7
+ #
8
+ # Copyright (c) 2017-2022 Agis Anastasopoulos
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
11
+ # this software and associated documentation files (the "Software"), to deal in
12
+ # the Software without restriction, including without limitation the rights to
13
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
14
+ # the Software, and to permit persons to whom the Software is furnished to do so,
15
+ # subject to the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be included in all
18
+ # copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
22
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
23
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
24
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ #
27
+ # This is a copy of https://github.com/agis/ruby-sdnotify as of commit cca575c
28
+ # The only changes made was "rehoming" it within the Puma module to avoid
29
+ # namespace collisions and applying standard's code formatting style.
30
+ #
31
+ # SdNotify is a pure-Ruby implementation of sd_notify(3). It can be used to
32
+ # notify systemd about state changes. Methods of this package are no-op on
33
+ # non-systemd systems (eg. Darwin).
34
+ #
35
+ # The API maps closely to the original implementation of sd_notify(3),
36
+ # therefore be sure to check the official man pages prior to using SdNotify.
37
+ #
38
+ # @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
39
+ module SdNotify
40
+ # Exception raised when there's an error writing to the notification socket
41
+ class NotifyError < RuntimeError; end
42
+
43
+ READY = "READY=1"
44
+ RELOADING = "RELOADING=1"
45
+ STOPPING = "STOPPING=1"
46
+ STATUS = "STATUS="
47
+ ERRNO = "ERRNO="
48
+ MAINPID = "MAINPID="
49
+ WATCHDOG = "WATCHDOG=1"
50
+ FDSTORE = "FDSTORE=1"
51
+
52
+ def self.ready(unset_env=false)
53
+ notify(READY, unset_env)
54
+ end
55
+
56
+ def self.reloading(unset_env=false)
57
+ notify(RELOADING, unset_env)
58
+ end
59
+
60
+ def self.stopping(unset_env=false)
61
+ notify(STOPPING, unset_env)
62
+ end
63
+
64
+ # @param status [String] a custom status string that describes the current
65
+ # state of the service
66
+ def self.status(status, unset_env=false)
67
+ notify("#{STATUS}#{status}", unset_env)
68
+ end
69
+
70
+ # @param errno [Integer]
71
+ def self.errno(errno, unset_env=false)
72
+ notify("#{ERRNO}#{errno}", unset_env)
73
+ end
74
+
75
+ # @param pid [Integer]
76
+ def self.mainpid(pid, unset_env=false)
77
+ notify("#{MAINPID}#{pid}", unset_env)
78
+ end
79
+
80
+ def self.watchdog(unset_env=false)
81
+ notify(WATCHDOG, unset_env)
82
+ end
83
+
84
+ def self.fdstore(unset_env=false)
85
+ notify(FDSTORE, unset_env)
86
+ end
87
+
88
+ # @param [Boolean] true if the service manager expects watchdog keep-alive
89
+ # notification messages to be sent from this process.
90
+ #
91
+ # If the $WATCHDOG_USEC environment variable is set,
92
+ # and the $WATCHDOG_PID variable is unset or set to the PID of the current
93
+ # process
94
+ #
95
+ # @note Unlike sd_watchdog_enabled(3), this method does not mutate the
96
+ # environment.
97
+ def self.watchdog?
98
+ wd_usec = ENV["WATCHDOG_USEC"]
99
+ wd_pid = ENV["WATCHDOG_PID"]
100
+
101
+ return false if !wd_usec
102
+
103
+ begin
104
+ wd_usec = Integer(wd_usec)
105
+ rescue
106
+ return false
107
+ end
108
+
109
+ return false if wd_usec <= 0
110
+ return true if !wd_pid || wd_pid == $$.to_s
111
+
112
+ false
113
+ end
114
+
115
+ # Notify systemd with the provided state, via the notification socket, if
116
+ # any.
117
+ #
118
+ # Generally this method will be used indirectly through the other methods
119
+ # of the library.
120
+ #
121
+ # @param state [String]
122
+ # @param unset_env [Boolean]
123
+ #
124
+ # @return [Fixnum, nil] the number of bytes written to the notification
125
+ # socket or nil if there was no socket to report to (eg. the program wasn't
126
+ # started by systemd)
127
+ #
128
+ # @raise [NotifyError] if there was an error communicating with the systemd
129
+ # socket
130
+ #
131
+ # @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
132
+ def self.notify(state, unset_env=false)
133
+ sock = ENV["NOTIFY_SOCKET"]
134
+
135
+ return nil if !sock
136
+
137
+ ENV.delete("NOTIFY_SOCKET") if unset_env
138
+
139
+ begin
140
+ Addrinfo.unix(sock, :DGRAM).connect do |s|
141
+ s.close_on_exec = true
142
+ s.write(state)
143
+ end
144
+ rescue StandardError => e
145
+ raise NotifyError, "#{e.class}: #{e.message}", e.backtrace
146
+ end
147
+ end
148
+ end
149
+ end
data/lib/puma/server.rb CHANGED
@@ -2,19 +2,19 @@
2
2
 
3
3
  require 'stringio'
4
4
 
5
- require 'puma/thread_pool'
6
- require 'puma/const'
7
- require 'puma/events'
8
- require 'puma/null_io'
9
- require 'puma/reactor'
10
- require 'puma/client'
11
- require 'puma/binder'
12
- require 'puma/util'
13
- require 'puma/io_buffer'
14
- require 'puma/request'
5
+ require_relative 'thread_pool'
6
+ require_relative 'const'
7
+ require_relative 'log_writer'
8
+ require_relative 'events'
9
+ require_relative 'null_io'
10
+ require_relative 'reactor'
11
+ require_relative 'client'
12
+ require_relative 'binder'
13
+ require_relative 'util'
14
+ require_relative 'request'
15
15
 
16
16
  require 'socket'
17
- require 'io/wait'
17
+ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
18
18
  require 'forwardable'
19
19
 
20
20
  module Puma
@@ -30,12 +30,12 @@ module Puma
30
30
  #
31
31
  # Each `Puma::Server` will have one reactor and one thread pool.
32
32
  class Server
33
-
34
33
  include Puma::Const
35
34
  include Request
36
35
  extend Forwardable
37
36
 
38
37
  attr_reader :thread
38
+ attr_reader :log_writer
39
39
  attr_reader :events
40
40
  attr_reader :min_threads, :max_threads # for #stats
41
41
  attr_reader :requests_count # @version 5.0.0
@@ -45,23 +45,19 @@ module Puma
45
45
  :leak_stack_on_error,
46
46
  :persistent_timeout, :reaping_time
47
47
 
48
- # @deprecated v6.0.0
49
- attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
50
- :leak_stack_on_error, :min_threads, :max_threads,
51
- :persistent_timeout, :reaping_time
52
-
53
48
  attr_accessor :app
54
49
  attr_accessor :binder
55
50
 
56
51
  def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
57
52
  :add_unix_listener, :connected_ports
58
53
 
59
- ThreadLocalKey = :puma_server
54
+ THREAD_LOCAL_KEY = :puma_server
60
55
 
61
56
  # Create a server for the rack app +app+.
62
57
  #
63
- # +events+ is an object which will be called when certain error events occur
64
- # to be handled. See Puma::Events for the list of current methods to implement.
58
+ # +log_writer+ is a Puma::LogWriter object used to log info and error messages.
59
+ #
60
+ # +events+ is a Puma::Events object used to notify application status events.
65
61
  #
66
62
  # Server#run returns a thread that you can join on to wait for the server
67
63
  # to do its work.
@@ -70,34 +66,53 @@ module Puma
70
66
  # and have default values set via +fetch+. Normally the values are set via
71
67
  # `::Puma::Configuration.puma_default_options`.
72
68
  #
73
- def initialize(app, events=Events.stdio, options={})
69
+ # @note The `events` parameter is set to nil, and set to `Events.new` in code.
70
+ # Often `options` needs to be passed, but `events` does not. Using nil allows
71
+ # calling code to not require events.rb.
72
+ #
73
+ def initialize(app, events = nil, options = {})
74
74
  @app = app
75
- @events = events
75
+ @events = events || Events.new
76
76
 
77
77
  @check, @notify = nil
78
78
  @status = :stop
79
79
 
80
- @auto_trim_time = 30
81
- @reaping_time = 1
82
-
83
80
  @thread = nil
84
81
  @thread_pool = nil
85
82
 
86
- @options = options
83
+ @options = if options.is_a?(UserFileDefaultOptions)
84
+ options
85
+ else
86
+ UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
87
+ end
87
88
 
88
- @early_hints = options.fetch :early_hints, nil
89
- @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
90
- @min_threads = options.fetch :min_threads, 0
91
- @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
92
- @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
93
- @queue_requests = options.fetch :queue_requests, true
94
- @max_fast_inline = options.fetch :max_fast_inline, MAX_FAST_INLINE
95
- @io_selector_backend = options.fetch :io_selector_backend, :auto
89
+ @log_writer = @options.fetch :log_writer, LogWriter.stdio
90
+ @early_hints = @options[:early_hints]
91
+ @first_data_timeout = @options[:first_data_timeout]
92
+ @min_threads = @options[:min_threads]
93
+ @max_threads = @options[:max_threads]
94
+ @persistent_timeout = @options[:persistent_timeout]
95
+ @queue_requests = @options[:queue_requests]
96
+ @max_fast_inline = @options[:max_fast_inline]
97
+ @io_selector_backend = @options[:io_selector_backend]
98
+ @http_content_length_limit = @options[:http_content_length_limit]
99
+
100
+ # make this a hash, since we prefer `key?` over `include?`
101
+ @supported_http_methods =
102
+ if @options[:supported_http_methods] == :any
103
+ :any
104
+ else
105
+ if (ary = @options[:supported_http_methods])
106
+ ary
107
+ else
108
+ SUPPORTED_HTTP_METHODS
109
+ end.sort.product([nil]).to_h.freeze
110
+ end
96
111
 
97
112
  temp = !!(@options[:environment] =~ /\A(development|test)\z/)
98
113
  @leak_stack_on_error = @options[:environment] ? temp : true
99
114
 
100
- @binder = Binder.new(events)
115
+ @binder = Binder.new(log_writer)
101
116
 
102
117
  ENV['RACK_ENV'] ||= "development"
103
118
 
@@ -115,7 +130,7 @@ module Puma
115
130
  class << self
116
131
  # @!attribute [r] current
117
132
  def current
118
- Thread.current[ThreadLocalKey]
133
+ Thread.current[THREAD_LOCAL_KEY]
119
134
  end
120
135
 
121
136
  # :nodoc:
@@ -193,12 +208,12 @@ module Puma
193
208
 
194
209
  # @!attribute [r] backlog
195
210
  def backlog
196
- @thread_pool and @thread_pool.backlog
211
+ @thread_pool&.backlog
197
212
  end
198
213
 
199
214
  # @!attribute [r] running
200
215
  def running
201
- @thread_pool and @thread_pool.spawned
216
+ @thread_pool&.spawned
202
217
  end
203
218
 
204
219
 
@@ -211,7 +226,7 @@ module Puma
211
226
  # value would be 4 until it finishes processing.
212
227
  # @!attribute [r] pool_capacity
213
228
  def pool_capacity
214
- @thread_pool and @thread_pool.pool_capacity
229
+ @thread_pool&.pool_capacity
215
230
  end
216
231
 
217
232
  # Runs the server.
@@ -220,36 +235,23 @@ module Puma
220
235
  # up in the background to handle requests. Otherwise requests
221
236
  # are handled synchronously.
222
237
  #
223
- def run(background=true, thread_name: 'server')
238
+ def run(background=true, thread_name: 'srv')
224
239
  BasicSocket.do_not_reverse_lookup = true
225
240
 
226
241
  @events.fire :state, :booting
227
242
 
228
243
  @status = :run
229
244
 
230
- @thread_pool = ThreadPool.new(
231
- thread_name,
232
- @min_threads,
233
- @max_threads,
234
- ::Puma::IOBuffer,
235
- &method(:process_client)
236
- )
237
-
238
- @thread_pool.out_of_band_hook = @options[:out_of_band]
239
- @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
245
+ @thread_pool = ThreadPool.new(thread_name, @options) { |client| process_client client }
240
246
 
241
247
  if @queue_requests
242
- @reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
248
+ @reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
243
249
  @reactor.run
244
250
  end
245
251
 
246
- if @reaping_time
247
- @thread_pool.auto_reap!(@reaping_time)
248
- end
249
252
 
250
- if @auto_trim_time
251
- @thread_pool.auto_trim!(@auto_trim_time)
252
- end
253
+ @thread_pool.auto_reap! if @options[:reaping_time]
254
+ @thread_pool.auto_trim! if @options[:auto_trim_time]
253
255
 
254
256
  @check, @notify = Puma::Util.pipe unless @notify
255
257
 
@@ -315,16 +317,15 @@ module Puma
315
317
  queue_requests = @queue_requests
316
318
  drain = @options[:drain_on_shutdown] ? 0 : nil
317
319
 
318
- remote_addr_value = nil
319
- remote_addr_header = nil
320
-
321
- case @options[:remote_address]
320
+ addr_send_name, addr_value = case @options[:remote_address]
322
321
  when :value
323
- remote_addr_value = @options[:remote_address_value]
322
+ [:peerip=, @options[:remote_address_value]]
324
323
  when :header
325
- remote_addr_header = @options[:remote_address_header]
324
+ [:remote_addr_header=, @options[:remote_address_header]]
326
325
  when :proxy_protocol
327
- remote_addr_proxy_protocol = @options[:remote_address_proxy_protocol]
326
+ [:expect_proxy_proto=, @options[:remote_address_proxy_protocol]]
327
+ else
328
+ [nil, nil]
328
329
  end
329
330
 
330
331
  while @status == :run || (drain && shutting_down?)
@@ -344,27 +345,22 @@ module Puma
344
345
  next
345
346
  end
346
347
  drain += 1 if shutting_down?
347
- client = Client.new io, @binder.env(sock)
348
- client.listener = sock
349
- if remote_addr_value
350
- client.peerip = remote_addr_value
351
- elsif remote_addr_header
352
- client.remote_addr_header = remote_addr_header
353
- elsif remote_addr_proxy_protocol
354
- client.expect_proxy_proto = remote_addr_proxy_protocol
355
- end
356
- pool << client
348
+ pool << Client.new(io, @binder.env(sock)).tap { |c|
349
+ c.listener = sock
350
+ c.http_content_length_limit = @http_content_length_limit
351
+ c.send(addr_send_name, addr_value) if addr_value
352
+ }
357
353
  end
358
354
  end
359
355
  rescue IOError, Errno::EBADF
360
356
  # In the case that any of the sockets are unexpectedly close.
361
357
  raise
362
358
  rescue StandardError => e
363
- @events.unknown_error e, nil, "Listen loop"
359
+ @log_writer.unknown_error e, nil, "Listen loop"
364
360
  end
365
361
  end
366
362
 
367
- @events.debug "Drained #{drain} additional connections." if drain
363
+ @log_writer.debug "Drained #{drain} additional connections." if drain
368
364
  @events.fire :state, @status
369
365
 
370
366
  if queue_requests
@@ -373,15 +369,15 @@ module Puma
373
369
  end
374
370
  graceful_shutdown if @status == :stop || @status == :restart
375
371
  rescue Exception => e
376
- @events.unknown_error e, nil, "Exception handling servers"
372
+ @log_writer.unknown_error e, nil, "Exception handling servers"
377
373
  ensure
378
- begin
379
- @check.close unless @check.closed?
380
- rescue Errno::EBADF, RuntimeError
381
- # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
382
- # Errno::EBADF is infrequently raised
374
+ # Errno::EBADF is infrequently raised
375
+ [@check, @notify].each do |io|
376
+ begin
377
+ io.close unless io.closed?
378
+ rescue Errno::EBADF
379
+ end
383
380
  end
384
- @notify.close
385
381
  @notify = nil
386
382
  @check = nil
387
383
  end
@@ -418,9 +414,9 @@ module Puma
418
414
  # returning.
419
415
  #
420
416
  # Return true if one or more requests were processed.
421
- def process_client(client, buffer)
417
+ def process_client(client)
422
418
  # Advertise this server into the thread
423
- Thread.current[ThreadLocalKey] = self
419
+ Thread.current[THREAD_LOCAL_KEY] = self
424
420
 
425
421
  clean_thread_locals = @options[:clean_thread_locals]
426
422
  close_socket = true
@@ -444,15 +440,13 @@ module Puma
444
440
 
445
441
  while true
446
442
  @requests_count += 1
447
- case handle_request(client, buffer, requests + 1)
443
+ case handle_request(client, requests + 1)
448
444
  when false
449
445
  break
450
446
  when :async
451
447
  close_socket = false
452
448
  break
453
449
  when true
454
- buffer.reset
455
-
456
450
  ThreadPool.clean_thread_locals if clean_thread_locals
457
451
 
458
452
  requests += 1
@@ -486,7 +480,7 @@ module Puma
486
480
  # The ensure tries to close +client+ down
487
481
  requests > 0
488
482
  ensure
489
- buffer.reset
483
+ client.io_buffer.reset
490
484
 
491
485
  begin
492
486
  client.close if close_socket
@@ -494,7 +488,7 @@ module Puma
494
488
  Puma::Util.purge_interrupt_queue
495
489
  # Already closed
496
490
  rescue StandardError => e
497
- @events.unknown_error e, nil, "Client"
491
+ @log_writer.unknown_error e, nil, "Client"
498
492
  end
499
493
  end
500
494
  end
@@ -517,13 +511,16 @@ module Puma
517
511
  lowlevel_error(e, client.env)
518
512
  case e
519
513
  when MiniSSL::SSLError
520
- @events.ssl_error e, client.io
514
+ @log_writer.ssl_error e, client.io
521
515
  when HttpParserError
522
516
  client.write_error(400)
523
- @events.parse_error e, client
517
+ @log_writer.parse_error e, client
518
+ when HttpParserError501
519
+ client.write_error(501)
520
+ @log_writer.parse_error e, client
524
521
  else
525
522
  client.write_error(500)
526
- @events.unknown_error e, nil, "Read"
523
+ @log_writer.unknown_error e, nil, "Read"
527
524
  end
528
525
  end
529
526
 
@@ -581,7 +578,7 @@ module Puma
581
578
 
582
579
  def notify_safely(message)
583
580
  @notify << message
584
- rescue IOError, NoMethodError, Errno::EPIPE
581
+ rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
585
582
  # The server, in another thread, is shutting down
586
583
  Puma::Util.purge_interrupt_queue
587
584
  rescue RuntimeError => e