puma 6.4.0 → 6.6.1

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +235 -18
  3. data/README.md +87 -26
  4. data/docs/fork_worker.md +11 -1
  5. data/docs/java_options.md +54 -0
  6. data/docs/kubernetes.md +1 -1
  7. data/docs/plugins.md +4 -0
  8. data/docs/restart.md +1 -0
  9. data/docs/signals.md +2 -2
  10. data/docs/stats.md +8 -3
  11. data/docs/systemd.md +13 -6
  12. data/ext/puma_http11/extconf.rb +21 -14
  13. data/ext/puma_http11/mini_ssl.c +16 -2
  14. data/ext/puma_http11/org/jruby/puma/Http11.java +31 -8
  15. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +2 -1
  16. data/ext/puma_http11/puma_http11.c +4 -1
  17. data/lib/puma/app/status.rb +1 -1
  18. data/lib/puma/binder.rb +12 -4
  19. data/lib/puma/cli.rb +9 -5
  20. data/lib/puma/client.rb +104 -16
  21. data/lib/puma/cluster/worker.rb +9 -6
  22. data/lib/puma/cluster/worker_handle.rb +4 -5
  23. data/lib/puma/cluster.rb +112 -27
  24. data/lib/puma/configuration.rb +37 -21
  25. data/lib/puma/const.rb +22 -3
  26. data/lib/puma/control_cli.rb +4 -4
  27. data/lib/puma/detect.rb +3 -4
  28. data/lib/puma/dsl.rb +303 -58
  29. data/lib/puma/error_logger.rb +4 -4
  30. data/lib/puma/jruby_restart.rb +0 -16
  31. data/lib/puma/launcher.rb +21 -8
  32. data/lib/puma/log_writer.rb +9 -9
  33. data/lib/puma/minissl/context_builder.rb +3 -0
  34. data/lib/puma/minissl.rb +6 -0
  35. data/lib/puma/null_io.rb +42 -2
  36. data/lib/puma/request.rb +35 -14
  37. data/lib/puma/runner.rb +10 -3
  38. data/lib/puma/sd_notify.rb +1 -4
  39. data/lib/puma/server.rb +53 -21
  40. data/lib/puma/single.rb +1 -1
  41. data/lib/puma/state_file.rb +2 -2
  42. data/lib/puma/thread_pool.rb +14 -2
  43. data/lib/puma/util.rb +1 -1
  44. data/lib/rack/handler/puma.rb +8 -5
  45. data/tools/Dockerfile +5 -3
  46. metadata +10 -12
@@ -15,14 +15,14 @@ module Puma
15
15
 
16
16
  LOG_QUEUE = Queue.new
17
17
 
18
- def initialize(ioerr)
18
+ def initialize(ioerr, env: ENV)
19
19
  @ioerr = ioerr
20
20
 
21
- @debug = ENV.key? 'PUMA_DEBUG'
21
+ @debug = env.key?('PUMA_DEBUG')
22
22
  end
23
23
 
24
- def self.stdio
25
- new $stderr
24
+ def self.stdio(env: ENV)
25
+ new($stderr, env: env)
26
26
  end
27
27
 
28
28
  # Print occurred error details.
@@ -6,22 +6,6 @@ module Puma
6
6
  module JRubyRestart
7
7
  extend FFI::Library
8
8
  ffi_lib 'c'
9
-
10
- attach_function :execlp, [:string, :varargs], :int
11
9
  attach_function :chdir, [:string], :int
12
- attach_function :fork, [], :int
13
- attach_function :exit, [:int], :void
14
- attach_function :setsid, [], :int
15
-
16
- def self.chdir_exec(dir, argv)
17
- chdir(dir)
18
- cmd = argv.first
19
- argv = ([:string] * argv.size).zip(argv)
20
- argv.flatten!
21
- argv << :string
22
- argv << nil
23
- execlp(cmd, *argv)
24
- raise SystemCallError.new(FFI.errno)
25
- end
26
10
  end
27
11
  end
data/lib/puma/launcher.rb CHANGED
@@ -46,6 +46,8 @@ module Puma
46
46
  @original_argv = @argv.dup
47
47
  @config = conf
48
48
 
49
+ env = launcher_args.delete(:env) || ENV
50
+
49
51
  @config.options[:log_writer] = @log_writer
50
52
 
51
53
  # Advertise the Configuration
@@ -62,7 +64,7 @@ module Puma
62
64
  # Load the systemd integration if we detect systemd's NOTIFY_SOCKET.
63
65
  # Skip this on JRuby though, because it is incompatible with the systemd
64
66
  # integration due to https://github.com/jruby/jruby/issues/6504
65
- if ENV["NOTIFY_SOCKET"] && !Puma.jruby?
67
+ if ENV["NOTIFY_SOCKET"] && !Puma.jruby? && !ENV["PUMA_SKIP_SYSTEMD"]
66
68
  @config.plugins.create('systemd')
67
69
  end
68
70
 
@@ -105,7 +107,7 @@ module Puma
105
107
 
106
108
  @status = :run
107
109
 
108
- log_config if ENV['PUMA_LOG_CONFIG']
110
+ log_config if env['PUMA_LOG_CONFIG']
109
111
  end
110
112
 
111
113
  attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir
@@ -165,6 +167,13 @@ module Puma
165
167
  log "* phased-restart called but not available, restarting normally."
166
168
  return restart
167
169
  end
170
+
171
+ if @options.file_options[:tag].nil?
172
+ dir = File.realdirpath(@restart_dir)
173
+ @options[:tag] = File.basename(dir)
174
+ set_process_title
175
+ end
176
+
168
177
  true
169
178
  end
170
179
 
@@ -287,7 +296,9 @@ module Puma
287
296
  close_binder_listeners
288
297
 
289
298
  require_relative 'jruby_restart'
290
- JRubyRestart.chdir_exec(@restart_dir, restart_args)
299
+ argv = restart_args
300
+ JRubyRestart.chdir(@restart_dir)
301
+ Kernel.exec(*argv)
291
302
  elsif Puma.windows?
292
303
  close_binder_listeners
293
304
 
@@ -408,12 +419,14 @@ module Puma
408
419
  end
409
420
 
410
421
  def setup_signals
411
- begin
412
- Signal.trap "SIGUSR2" do
413
- restart
422
+ unless ENV["PUMA_SKIP_SIGUSR2"]
423
+ begin
424
+ Signal.trap "SIGUSR2" do
425
+ restart
426
+ end
427
+ rescue Exception
428
+ log "*** SIGUSR2 not implemented, signal based restart unavailable!"
414
429
  end
415
- rescue Exception
416
- log "*** SIGUSR2 not implemented, signal based restart unavailable!"
417
430
  end
418
431
 
419
432
  unless Puma.jruby?
@@ -31,31 +31,31 @@ module Puma
31
31
  attr_accessor :formatter, :custom_logger
32
32
 
33
33
  # Create a LogWriter that prints to +stdout+ and +stderr+.
34
- def initialize(stdout, stderr)
34
+ def initialize(stdout, stderr, env: ENV)
35
35
  @formatter = DefaultFormatter.new
36
36
  @custom_logger = nil
37
37
  @stdout = stdout
38
38
  @stderr = stderr
39
39
 
40
- @debug = ENV.key?('PUMA_DEBUG')
41
- @error_logger = ErrorLogger.new(@stderr)
40
+ @debug = env.key?('PUMA_DEBUG')
41
+ @error_logger = ErrorLogger.new(@stderr, env: env)
42
42
  end
43
43
 
44
44
  DEFAULT = new(STDOUT, STDERR)
45
45
 
46
46
  # Returns an LogWriter object which writes its status to
47
47
  # two StringIO objects.
48
- def self.strings
49
- LogWriter.new(StringIO.new, StringIO.new)
48
+ def self.strings(env: ENV)
49
+ LogWriter.new(StringIO.new, StringIO.new, env: env)
50
50
  end
51
51
 
52
- def self.stdio
53
- LogWriter.new($stdout, $stderr)
52
+ def self.stdio(env: ENV)
53
+ LogWriter.new($stdout, $stderr, env: env)
54
54
  end
55
55
 
56
- def self.null
56
+ def self.null(env: ENV)
57
57
  n = NullIO.new
58
- LogWriter.new(n, n)
58
+ LogWriter.new(n, n, env: env)
59
59
  end
60
60
 
61
61
  # Write +str+ to +@stdout+
@@ -51,10 +51,13 @@ module Puma
51
51
  unless params['ca']
52
52
  log_writer.error "Please specify the SSL ca via 'ca='"
53
53
  end
54
+ # needed for Puma::MiniSSL::Socket#peercert, env['puma.peercert']
55
+ require 'openssl'
54
56
  end
55
57
 
56
58
  ctx.ca = params['ca'] if params['ca']
57
59
  ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
60
+ ctx.ssl_ciphersuites = params['ssl_ciphersuites'] if params['ssl_ciphersuites'] && HAS_TLS1_3
58
61
 
59
62
  ctx.reuse = params['reuse'] if params['reuse']
60
63
  end
data/lib/puma/minissl.rb CHANGED
@@ -184,6 +184,11 @@ module Puma
184
184
  @socket.peeraddr
185
185
  end
186
186
 
187
+ # OpenSSL is loaded in `MiniSSL::ContextBuilder` when
188
+ # `MiniSSL::Context#verify_mode` is not `VERIFY_NONE`.
189
+ # When `VERIFY_NONE`, `MiniSSL::Engine#peercert` is nil, regardless of
190
+ # whether the client sends a cert.
191
+ # @return [OpenSSL::X509::Certificate, nil]
187
192
  # @!attribute [r] peercert
188
193
  def peercert
189
194
  return @peercert if @peercert
@@ -284,6 +289,7 @@ module Puma
284
289
  attr_reader :cert_pem
285
290
  attr_reader :key_pem
286
291
  attr_accessor :ssl_cipher_filter
292
+ attr_accessor :ssl_ciphersuites
287
293
  attr_accessor :verification_flags
288
294
 
289
295
  attr_reader :reuse, :reuse_cache_size, :reuse_timeout
data/lib/puma/null_io.rb CHANGED
@@ -16,15 +16,38 @@ module Puma
16
16
  def each
17
17
  end
18
18
 
19
+ def pos
20
+ 0
21
+ end
22
+
19
23
  # Mimics IO#read with no data.
20
24
  #
21
- def read(count = nil, _buffer = nil)
22
- count && count > 0 ? nil : ""
25
+ def read(length = nil, buffer = nil)
26
+ if length.to_i < 0
27
+ raise ArgumentError, "(negative length #{length} given)"
28
+ end
29
+
30
+ buffer = if buffer.nil?
31
+ "".b
32
+ else
33
+ String.try_convert(buffer) or raise TypeError, "no implicit conversion of #{buffer.class} into String"
34
+ end
35
+ buffer.clear
36
+ if length.to_i > 0
37
+ nil
38
+ else
39
+ buffer
40
+ end
23
41
  end
24
42
 
25
43
  def rewind
26
44
  end
27
45
 
46
+ def seek(pos, whence = 0)
47
+ raise ArgumentError, "negative length #{pos} given" if pos.negative?
48
+ 0
49
+ end
50
+
28
51
  def close
29
52
  end
30
53
 
@@ -57,5 +80,22 @@ module Puma
57
80
  def closed?
58
81
  false
59
82
  end
83
+
84
+ def set_encoding(enc)
85
+ self
86
+ end
87
+
88
+ # per rack spec
89
+ def external_encoding
90
+ Encoding::ASCII_8BIT
91
+ end
92
+
93
+ def binmode
94
+ self
95
+ end
96
+
97
+ def binmode?
98
+ true
99
+ end
60
100
  end
61
101
  end
data/lib/puma/request.rb CHANGED
@@ -53,7 +53,6 @@ module Puma
53
53
  socket = client.io # io may be a MiniSSL::Socket
54
54
  app_body = nil
55
55
 
56
-
57
56
  return false if closed_socket?(socket)
58
57
 
59
58
  if client.http_content_length_limit_exceeded
@@ -135,13 +134,16 @@ module Puma
135
134
  io_buffer.reset
136
135
  uncork_socket client.io
137
136
  app_body.close if app_body.respond_to? :close
138
- client.tempfile&.unlink
139
- after_reply = env[RACK_AFTER_REPLY] || []
140
- begin
141
- after_reply.each { |o| o.call }
142
- rescue StandardError => e
143
- @log_writer.debug_error e
144
- end unless after_reply.empty?
137
+ client&.tempfile_close
138
+ if after_reply = env[RACK_AFTER_REPLY]
139
+ after_reply.each do |o|
140
+ begin
141
+ o.call
142
+ rescue StandardError => e
143
+ @log_writer.debug_error e
144
+ end
145
+ end
146
+ end
145
147
  end
146
148
 
147
149
  # Assembles the headers and prepares the body for actually sending the
@@ -165,9 +167,14 @@ module Puma
165
167
  # if the server is at capacity and the listener has a new connection ready.
166
168
  # This allows Puma to service connections fairly when the number
167
169
  # of concurrent connections exceeds the size of the threadpool.
168
- force_keep_alive = requests < @max_fast_inline ||
170
+ force_keep_alive = if @enable_keep_alives
171
+ requests < @max_fast_inline ||
169
172
  @thread_pool.busy_threads < @max_threads ||
170
173
  !client.listener.to_io.wait_readable(0)
174
+ else
175
+ # Always set force_keep_alive to false if the server has keep-alives not enabled.
176
+ false
177
+ end
171
178
 
172
179
  resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
173
180
 
@@ -187,7 +194,8 @@ module Puma
187
194
  elsif res_body.is_a?(File) && res_body.respond_to?(:size)
188
195
  body = res_body
189
196
  content_length = body.size
190
- elsif res_body.respond_to?(:to_path) && File.readable?(fn = res_body.to_path)
197
+ elsif res_body.respond_to?(:to_path) && (fn = res_body.to_path) &&
198
+ File.readable?(fn)
191
199
  body = File.open fn, 'rb'
192
200
  content_length = body.size
193
201
  close_body = true
@@ -195,7 +203,7 @@ module Puma
195
203
  body = res_body
196
204
  end
197
205
  elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
198
- File.readable?(fn = res_body.to_path)
206
+ (fn = res_body.to_path) && File.readable?(fn = res_body.to_path)
199
207
  body = File.open fn, 'rb'
200
208
  content_length = body.size
201
209
  close_body = true
@@ -495,6 +503,11 @@ module Puma
495
503
  # compatibility, we'll convert them back. This code is written to
496
504
  # avoid allocation in the common case (ie there are no headers
497
505
  # with `,` in their names), that's why it has the extra conditionals.
506
+ #
507
+ # @note If a normalized version of a `,` header already exists, we ignore
508
+ # the `,` version. This prevents clobbering headers managed by proxies
509
+ # but not by clients (Like X-Forwarded-For).
510
+ #
498
511
  # @param env [Hash] see Puma::Client#env, from request, modifies in place
499
512
  # @version 5.0.3
500
513
  #
@@ -503,23 +516,31 @@ module Puma
503
516
  to_add = nil
504
517
 
505
518
  env.each do |k,v|
506
- if k.start_with?("HTTP_") && k.include?(",") && k != "HTTP_TRANSFER,ENCODING"
519
+ if k.start_with?("HTTP_") && k.include?(",") && !UNMASKABLE_HEADERS.key?(k)
507
520
  if to_delete
508
521
  to_delete << k
509
522
  else
510
523
  to_delete = [k]
511
524
  end
512
525
 
526
+ new_k = k.tr(",", "_")
527
+ if env.key?(new_k)
528
+ next
529
+ end
530
+
513
531
  unless to_add
514
532
  to_add = {}
515
533
  end
516
534
 
517
- to_add[k.tr(",", "_")] = v
535
+ to_add[new_k] = v
518
536
  end
519
537
  end
520
538
 
521
- if to_delete
539
+ if to_delete # rubocop:disable Style/SafeNavigation
522
540
  to_delete.each { |k| env.delete(k) }
541
+ end
542
+
543
+ if to_add
523
544
  env.merge! to_add
524
545
  end
525
546
  end
data/lib/puma/runner.rb CHANGED
@@ -8,6 +8,9 @@ module Puma
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
+
12
+ include ::Puma::Const::PipeRequest
13
+
11
14
  def initialize(launcher)
12
15
  @launcher = launcher
13
16
  @log_writer = launcher.log_writer
@@ -27,7 +30,7 @@ module Puma
27
30
  def wakeup!
28
31
  return unless @wakeup
29
32
 
30
- @wakeup.write "!" unless @wakeup.closed?
33
+ @wakeup.write PIPE_WAKEUP unless @wakeup.closed?
31
34
 
32
35
  rescue SystemCallError, IOError
33
36
  Puma::Util.purge_interrupt_queue
@@ -70,7 +73,7 @@ module Puma
70
73
 
71
74
  app = Puma::App::Status.new @launcher, token
72
75
 
73
- # A Reactor is not created aand nio4r is not loaded when 'queue_requests: false'
76
+ # A Reactor is not created and nio4r is not loaded when 'queue_requests: false'
74
77
  # Use `nil` for events, no hooks in control server
75
78
  control = Puma::Server.new app, nil,
76
79
  { min_threads: 0, max_threads: 1, queue_requests: false, log_writer: @log_writer }
@@ -91,7 +94,10 @@ module Puma
91
94
  end
92
95
 
93
96
  # @!attribute [r] ruby_engine
97
+ # @deprecated Use `RUBY_DESCRIPTION` instead
94
98
  def ruby_engine
99
+ warn "Puma::Runner#ruby_engine is deprecated; use RUBY_DESCRIPTION instead. It will be removed in puma v7."
100
+
95
101
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
96
102
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
97
103
  else
@@ -109,7 +115,8 @@ module Puma
109
115
  environment = @options[:environment]
110
116
 
111
117
  log "Puma starting in #{mode} mode..."
112
- log "* Puma version: #{Puma::Const::PUMA_VERSION} (#{ruby_engine}) (\"#{Puma::Const::CODE_NAME}\")"
118
+ log "* Puma version: #{Puma::Const::PUMA_VERSION} (\"#{Puma::Const::CODE_NAME}\")"
119
+ log "* Ruby version: #{RUBY_DESCRIPTION}"
113
120
  log "* Min threads: #{min_t}"
114
121
  log "* Max threads: #{max_t}"
115
122
  log "* Environment: #{environment}"
@@ -137,10 +137,7 @@ module Puma
137
137
  ENV.delete("NOTIFY_SOCKET") if unset_env
138
138
 
139
139
  begin
140
- Addrinfo.unix(sock, :DGRAM).connect do |s|
141
- s.close_on_exec = true
142
- s.write(state)
143
- end
140
+ Addrinfo.unix(sock, :DGRAM).connect { |s| s.write state }
144
141
  rescue StandardError => e
145
142
  raise NotifyError, "#{e.class}: #{e.message}", e.backtrace
146
143
  end
data/lib/puma/server.rb CHANGED
@@ -18,6 +18,9 @@ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
18
18
 
19
19
  module Puma
20
20
 
21
+ # This method was private on Ruby 2.4 but became public on Ruby 2.5+:
22
+ Thread.send(:attr_accessor, :puma_server)
23
+
21
24
  # The HTTP Server itself. Serves out a single Rack app.
22
25
  #
23
26
  # This class is used by the `Puma::Single` and `Puma::Cluster` classes
@@ -32,6 +35,7 @@ module Puma
32
35
  include Puma::Const
33
36
  include Request
34
37
 
38
+ attr_reader :options
35
39
  attr_reader :thread
36
40
  attr_reader :log_writer
37
41
  attr_reader :events
@@ -46,7 +50,6 @@ module Puma
46
50
  attr_accessor :app
47
51
  attr_accessor :binder
48
52
 
49
- THREAD_LOCAL_KEY = :puma_server
50
53
 
51
54
  # Create a server for the rack app +app+.
52
55
  #
@@ -81,6 +84,8 @@ module Puma
81
84
  UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
82
85
  end
83
86
 
87
+ @clustered = (@options.fetch :workers, 0) > 0
88
+ @worker_write = @options[:worker_write]
84
89
  @log_writer = @options.fetch :log_writer, LogWriter.stdio
85
90
  @early_hints = @options[:early_hints]
86
91
  @first_data_timeout = @options[:first_data_timeout]
@@ -90,6 +95,7 @@ module Puma
90
95
  @max_threads = @options[:max_threads]
91
96
  @queue_requests = @options[:queue_requests]
92
97
  @max_fast_inline = @options[:max_fast_inline]
98
+ @enable_keep_alives = @options[:enable_keep_alives]
93
99
  @io_selector_backend = @options[:io_selector_backend]
94
100
  @http_content_length_limit = @options[:http_content_length_limit]
95
101
 
@@ -117,6 +123,8 @@ module Puma
117
123
  @precheck_closing = true
118
124
 
119
125
  @requests_count = 0
126
+
127
+ @idle_timeout_reached = false
120
128
  end
121
129
 
122
130
  def inherit_binder(bind)
@@ -126,7 +134,7 @@ module Puma
126
134
  class << self
127
135
  # @!attribute [r] current
128
136
  def current
129
- Thread.current[THREAD_LOCAL_KEY]
137
+ Thread.current.puma_server
130
138
  end
131
139
 
132
140
  # :nodoc:
@@ -225,6 +233,11 @@ module Puma
225
233
  @thread_pool&.pool_capacity
226
234
  end
227
235
 
236
+ # @!attribute [r] busy_threads
237
+ def busy_threads
238
+ @thread_pool&.busy_threads
239
+ end
240
+
228
241
  # Runs the server.
229
242
  #
230
243
  # If +background+ is true (the default) then a thread is spun
@@ -238,16 +251,15 @@ module Puma
238
251
 
239
252
  @status = :run
240
253
 
241
- @thread_pool = ThreadPool.new(thread_name, @options) { |client| process_client client }
254
+ @thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
242
255
 
243
256
  if @queue_requests
244
257
  @reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
245
258
  @reactor.run
246
259
  end
247
260
 
248
-
249
- @thread_pool.auto_reap! if @options[:reaping_time]
250
- @thread_pool.auto_trim! if @options[:auto_trim_time]
261
+ @thread_pool.auto_reap! if options[:reaping_time]
262
+ @thread_pool.auto_trim! if @min_threads != @max_threads && options[:auto_trim_time]
251
263
 
252
264
  @check, @notify = Puma::Util.pipe unless @notify
253
265
 
@@ -311,15 +323,15 @@ module Puma
311
323
  sockets = [check] + @binder.ios
312
324
  pool = @thread_pool
313
325
  queue_requests = @queue_requests
314
- drain = @options[:drain_on_shutdown] ? 0 : nil
326
+ drain = options[:drain_on_shutdown] ? 0 : nil
315
327
 
316
- addr_send_name, addr_value = case @options[:remote_address]
328
+ addr_send_name, addr_value = case options[:remote_address]
317
329
  when :value
318
- [:peerip=, @options[:remote_address_value]]
330
+ [:peerip=, options[:remote_address_value]]
319
331
  when :header
320
- [:remote_addr_header=, @options[:remote_address_header]]
332
+ [:remote_addr_header=, options[:remote_address_header]]
321
333
  when :proxy_protocol
322
- [:expect_proxy_proto=, @options[:remote_address_proxy_protocol]]
334
+ [:expect_proxy_proto=, options[:remote_address_proxy_protocol]]
323
335
  else
324
336
  [nil, nil]
325
337
  end
@@ -328,16 +340,32 @@ module Puma
328
340
  begin
329
341
  ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
330
342
  unless ios
331
- @status = :stop unless shutting_down?
343
+ unless shutting_down?
344
+ @idle_timeout_reached = true
345
+
346
+ if @clustered
347
+ @worker_write << "#{PipeRequest::PIPE_IDLE}#{Process.pid}\n" rescue nil
348
+ next
349
+ else
350
+ @log_writer.log "- Idle timeout reached"
351
+ @status = :stop
352
+ end
353
+ end
354
+
332
355
  break
333
356
  end
334
357
 
358
+ if @idle_timeout_reached && @clustered
359
+ @idle_timeout_reached = false
360
+ @worker_write << "#{PipeRequest::PIPE_IDLE}#{Process.pid}\n" rescue nil
361
+ end
362
+
335
363
  ios.first.each do |sock|
336
364
  if sock == check
337
365
  break if handle_check
338
366
  else
339
367
  pool.wait_until_not_full
340
- pool.wait_for_less_busy_worker(@options[:wait_for_less_busy_worker])
368
+ pool.wait_for_less_busy_worker(options[:wait_for_less_busy_worker]) if @clustered
341
369
 
342
370
  io = begin
343
371
  sock.accept_nonblock
@@ -367,6 +395,7 @@ module Puma
367
395
  @queue_requests = false
368
396
  @reactor.shutdown
369
397
  end
398
+
370
399
  graceful_shutdown if @status == :stop || @status == :restart
371
400
  rescue Exception => e
372
401
  @log_writer.unknown_error e, nil, "Exception handling servers"
@@ -416,9 +445,9 @@ module Puma
416
445
  # Return true if one or more requests were processed.
417
446
  def process_client(client)
418
447
  # Advertise this server into the thread
419
- Thread.current[THREAD_LOCAL_KEY] = self
448
+ Thread.current.puma_server = self
420
449
 
421
- clean_thread_locals = @options[:clean_thread_locals]
450
+ clean_thread_locals = options[:clean_thread_locals]
422
451
  close_socket = true
423
452
 
424
453
  requests = 0
@@ -527,7 +556,7 @@ module Puma
527
556
  # A fallback rack response if +@app+ raises as exception.
528
557
  #
529
558
  def lowlevel_error(e, env, status=500)
530
- if handler = @options[:lowlevel_error_handler]
559
+ if handler = options[:lowlevel_error_handler]
531
560
  if handler.arity == 1
532
561
  return handler.call(e)
533
562
  elsif handler.arity == 2
@@ -548,14 +577,13 @@ module Puma
548
577
  def response_to_error(client, requests, err, status_code)
549
578
  status, headers, res_body = lowlevel_error(err, client.env, status_code)
550
579
  prepare_response(status, headers, res_body, requests, client)
551
- client.write_error(status_code)
552
580
  end
553
581
  private :response_to_error
554
582
 
555
583
  # Wait for all outstanding requests to finish.
556
584
  #
557
585
  def graceful_shutdown
558
- if @options[:shutdown_debug]
586
+ if options[:shutdown_debug]
559
587
  threads = Thread.list
560
588
  total = threads.size
561
589
 
@@ -575,7 +603,7 @@ module Puma
575
603
  end
576
604
 
577
605
  if @thread_pool
578
- if timeout = @options[:force_shutdown_after]
606
+ if timeout = options[:force_shutdown_after]
579
607
  @thread_pool.shutdown timeout.to_f
580
608
  else
581
609
  @thread_pool.shutdown
@@ -622,13 +650,17 @@ module Puma
622
650
 
623
651
  # List of methods invoked by #stats.
624
652
  # @version 5.0.0
625
- STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
653
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count, :busy_threads].freeze
626
654
 
627
655
  # Returns a hash of stats about the running server for reporting purposes.
628
656
  # @version 5.0.0
629
657
  # @!attribute [r] stats
658
+ # @return [Hash] hash containing stat info from `Server` and `ThreadPool`
630
659
  def stats
631
- STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
660
+ stats = @thread_pool&.stats || {}
661
+ stats[:max_threads] = @max_threads
662
+ stats[:requests_count] = @requests_count
663
+ stats
632
664
  end
633
665
 
634
666
  # below are 'delegations' to binder
data/lib/puma/single.rb CHANGED
@@ -17,7 +17,7 @@ module Puma
17
17
  def stats
18
18
  {
19
19
  started_at: utc_iso8601(@started_at)
20
- }.merge(@server.stats).merge(super)
20
+ }.merge(@server&.stats || {}).merge(super)
21
21
  end
22
22
 
23
23
  def restart
@@ -56,11 +56,11 @@ module Puma
56
56
  end
57
57
 
58
58
  ALLOWED_FIELDS.each do |f|
59
- define_method f do
59
+ define_method f.to_sym do
60
60
  @options[f]
61
61
  end
62
62
 
63
- define_method "#{f}=" do |v|
63
+ define_method :"#{f}=" do |v|
64
64
  @options[f] = v
65
65
  end
66
66
  end