puma 6.4.1 → 7.2.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +407 -8
  3. data/README.md +109 -49
  4. data/docs/deployment.md +58 -23
  5. data/docs/fork_worker.md +11 -1
  6. data/docs/java_options.md +54 -0
  7. data/docs/jungle/README.md +1 -1
  8. data/docs/kubernetes.md +11 -16
  9. data/docs/plugins.md +6 -2
  10. data/docs/restart.md +2 -2
  11. data/docs/signals.md +21 -21
  12. data/docs/stats.md +11 -5
  13. data/docs/systemd.md +14 -5
  14. data/ext/puma_http11/extconf.rb +20 -32
  15. data/ext/puma_http11/mini_ssl.c +29 -9
  16. data/ext/puma_http11/org/jruby/puma/Http11.java +40 -9
  17. data/ext/puma_http11/puma_http11.c +125 -118
  18. data/lib/puma/app/status.rb +11 -3
  19. data/lib/puma/binder.rb +21 -11
  20. data/lib/puma/cli.rb +10 -8
  21. data/lib/puma/client.rb +183 -83
  22. data/lib/puma/cluster/worker.rb +24 -21
  23. data/lib/puma/cluster/worker_handle.rb +38 -8
  24. data/lib/puma/cluster.rb +73 -47
  25. data/lib/puma/cluster_accept_loop_delay.rb +91 -0
  26. data/lib/puma/commonlogger.rb +3 -3
  27. data/lib/puma/configuration.rb +131 -60
  28. data/lib/puma/const.rb +31 -12
  29. data/lib/puma/control_cli.rb +10 -6
  30. data/lib/puma/detect.rb +2 -0
  31. data/lib/puma/dsl.rb +411 -121
  32. data/lib/puma/error_logger.rb +7 -5
  33. data/lib/puma/events.rb +25 -10
  34. data/lib/puma/io_buffer.rb +8 -4
  35. data/lib/puma/jruby_restart.rb +0 -16
  36. data/lib/puma/launcher/bundle_pruner.rb +1 -1
  37. data/lib/puma/launcher.rb +73 -55
  38. data/lib/puma/log_writer.rb +9 -9
  39. data/lib/puma/minissl/context_builder.rb +1 -0
  40. data/lib/puma/minissl.rb +1 -1
  41. data/lib/puma/null_io.rb +26 -0
  42. data/lib/puma/plugin/systemd.rb +3 -3
  43. data/lib/puma/rack/urlmap.rb +1 -1
  44. data/lib/puma/reactor.rb +19 -13
  45. data/lib/puma/request.rb +71 -39
  46. data/lib/puma/runner.rb +15 -17
  47. data/lib/puma/sd_notify.rb +1 -4
  48. data/lib/puma/server.rb +134 -73
  49. data/lib/puma/single.rb +7 -4
  50. data/lib/puma/state_file.rb +3 -2
  51. data/lib/puma/thread_pool.rb +57 -80
  52. data/lib/puma/util.rb +0 -7
  53. data/lib/puma.rb +10 -0
  54. data/lib/rack/handler/puma.rb +10 -7
  55. data/tools/Dockerfile +15 -5
  56. metadata +14 -15
  57. data/ext/puma_http11/ext_help.h +0 -15
@@ -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.
@@ -78,10 +78,12 @@ module Puma
78
78
  def request_title(req)
79
79
  env = req.env
80
80
 
81
+ query_string = env[QUERY_STRING]
82
+
81
83
  REQUEST_FORMAT % [
82
84
  env[REQUEST_METHOD],
83
85
  env[REQUEST_PATH] || env[PATH_INFO],
84
- env[QUERY_STRING] || "",
86
+ query_string.nil? || query_string.empty? ? "" : "?#{query_string}",
85
87
  env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
86
88
  ]
87
89
  end
data/lib/puma/events.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Puma
4
4
 
5
5
  # This is an event sink used by `Puma::Server` to handle
6
- # lifecycle events such as :on_booted, :on_restart, and :on_stopped.
6
+ # lifecycle events such as :after_booted, :before_restart, and :after_stopped.
7
7
  # Using `Puma::DSL` it is possible to register callback hooks
8
8
  # for each event type.
9
9
  class Events
@@ -30,28 +30,43 @@ module Puma
30
30
  h
31
31
  end
32
32
 
33
+ def after_booted(&block)
34
+ register(:after_booted, &block)
35
+ end
36
+
37
+ def before_restart(&block)
38
+ register(:before_restart, &block)
39
+ end
40
+
41
+ def after_stopped(&block)
42
+ register(:after_stopped, &block)
43
+ end
44
+
33
45
  def on_booted(&block)
34
- register(:on_booted, &block)
46
+ Puma.deprecate_method_change :on_booted, __callee__, :after_booted
47
+ after_booted(&block)
35
48
  end
36
49
 
37
50
  def on_restart(&block)
38
- register(:on_restart, &block)
51
+ Puma.deprecate_method_change :on_restart, __callee__, :before_restart
52
+ before_restart(&block)
39
53
  end
40
54
 
41
55
  def on_stopped(&block)
42
- register(:on_stopped, &block)
56
+ Puma.deprecate_method_change :on_stopped, __callee__, :after_stopped
57
+ after_stopped(&block)
43
58
  end
44
59
 
45
- def fire_on_booted!
46
- fire(:on_booted)
60
+ def fire_after_booted!
61
+ fire(:after_booted)
47
62
  end
48
63
 
49
- def fire_on_restart!
50
- fire(:on_restart)
64
+ def fire_before_restart!
65
+ fire(:before_restart)
51
66
  end
52
67
 
53
- def fire_on_stopped!
54
- fire(:on_stopped)
68
+ def fire_after_stopped!
69
+ fire(:after_stopped)
55
70
  end
56
71
  end
57
72
  end
@@ -34,13 +34,17 @@ module Puma
34
34
 
35
35
  alias_method :clear, :reset
36
36
 
37
- # before Ruby 2.5, `write` would only take one argument
38
- if RUBY_VERSION >= '2.5' && RUBY_ENGINE != 'truffleruby'
39
- alias_method :append, :write
40
- else
37
+ # Create an `IoBuffer#append` method that accepts multiple strings and writes them
38
+ if RUBY_ENGINE == 'truffleruby'
39
+ # truffleruby (24.2.1, like ruby 3.3.7)
40
+ # StringIO.new.write("a", "b") # => `write': wrong number of arguments (given 2, expected 1) (ArgumentError)
41
41
  def append(*strs)
42
42
  strs.each { |str| write str }
43
43
  end
44
+ else
45
+ # Ruby 3+
46
+ # StringIO.new.write("a", "b") # => 2
47
+ alias_method :append, :write
44
48
  end
45
49
  end
46
50
  end
@@ -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
@@ -37,7 +37,7 @@ module Puma
37
37
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
38
38
  ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
39
39
  args = [Gem.ruby, puma_wild_path, '-I', dirs.join(':')] + @original_argv
40
- # Ruby 2.0+ defaults to true which breaks socket activation
40
+ # Defaults to true which breaks socket activation
41
41
  args += [{:close_others => false}]
42
42
  Kernel.exec(*args)
43
43
  end
data/lib/puma/launcher.rb CHANGED
@@ -22,12 +22,15 @@ module Puma
22
22
  #
23
23
  # +conf+ A Puma::Configuration object indicating how to run the server.
24
24
  #
25
- # +launcher_args+ A Hash that currently has one required key `:events`,
26
- # this is expected to hold an object similar to an `Puma::LogWriter.stdio`,
27
- # this object will be responsible for broadcasting Puma's internal state
28
- # to a logging destination. An optional key `:argv` can be supplied,
29
- # this should be an array of strings, these arguments are re-used when
30
- # restarting the puma server.
25
+ # +launcher_args+ A Hash that has a few optional keys.
26
+ # - +:log_writer+:: Expected to hold an object similar to `Puma::LogWriter.stdio`.
27
+ # This object will be responsible for broadcasting Puma's internal state
28
+ # to a logging destination.
29
+ # - +:events+:: Expected to hold an object similar to `Puma::Events`.
30
+ # - +:argv+:: Expected to be an array of strings.
31
+ # - +:env+:: Expected to hold a hash of environment variables.
32
+ #
33
+ # These arguments are re-used when restarting the puma server.
31
34
  #
32
35
  # Examples:
33
36
  #
@@ -39,64 +42,67 @@ module Puma
39
42
  # end
40
43
  # Puma::Launcher.new(conf, log_writer: Puma::LogWriter.stdio).run
41
44
  def initialize(conf, launcher_args={})
42
- @runner = nil
43
- @log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
44
- @events = launcher_args[:events] || Events.new
45
- @argv = launcher_args[:argv] || []
45
+ ## Minimal initialization before potential early restart (e.g. from bundle pruning)
46
+
47
+ @config = conf
48
+ # Advertise the CLI Configuration before config files are loaded
49
+ Puma.cli_config = @config if defined?(Puma.cli_config)
50
+ @config.clamp
51
+
52
+ @options = @config.options
53
+
54
+ @log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
55
+ @log_writer.formatter = LogWriter::PidFormatter.new if clustered?
56
+ @log_writer.formatter = @options[:log_formatter] if @options[:log_formatter]
57
+ @log_writer.custom_logger = @options[:custom_logger] if @options[:custom_logger]
58
+ @options[:log_writer] = @log_writer
59
+ @options[:logger] = @log_writer if clustered?
60
+
61
+ @events = launcher_args[:events] || Events.new
62
+
63
+ @argv = launcher_args[:argv] || []
46
64
  @original_argv = @argv.dup
47
- @config = conf
48
65
 
49
- @config.options[:log_writer] = @log_writer
66
+ ## End minimal initialization
50
67
 
51
- # Advertise the Configuration
52
- Puma.cli_config = @config if defined?(Puma.cli_config)
68
+ generate_restart_data
69
+ Dir.chdir(@restart_dir)
70
+
71
+ prune_bundler!
72
+
73
+ env = launcher_args.delete(:env) || ENV
53
74
 
54
- @config.load
75
+ # Log after prune_bundler! to avoid duplicate logging if a restart occurs
76
+ log_config if env['PUMA_LOG_CONFIG']
55
77
 
56
- @binder = Binder.new(@log_writer, conf)
57
- @binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
58
- @binder.create_activated_fds(ENV).each { |k| ENV.delete k }
78
+ @binder = Binder.new(@log_writer, @options)
79
+ @binder.create_inherited_fds(env).each { |k| env.delete k }
80
+ @binder.create_activated_fds(env).each { |k| env.delete k }
59
81
 
60
- @environment = conf.environment
82
+ @environment = @config.environment
61
83
 
62
84
  # Load the systemd integration if we detect systemd's NOTIFY_SOCKET.
63
85
  # Skip this on JRuby though, because it is incompatible with the systemd
64
86
  # integration due to https://github.com/jruby/jruby/issues/6504
65
- if ENV["NOTIFY_SOCKET"] && !Puma.jruby?
87
+ if ENV["NOTIFY_SOCKET"] && !Puma.jruby? && !ENV["PUMA_SKIP_SYSTEMD"]
66
88
  @config.plugins.create('systemd')
67
89
  end
68
90
 
69
- if @config.options[:bind_to_activated_sockets]
70
- @config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
71
- @config.options[:binds],
72
- @config.options[:bind_to_activated_sockets] == 'only'
91
+ if @options[:bind_to_activated_sockets]
92
+ @options[:binds] = @binder.synthesize_binds_from_activated_fs(
93
+ @options[:binds],
94
+ @options[:bind_to_activated_sockets] == 'only'
73
95
  )
74
96
  end
75
97
 
76
- @options = @config.options
77
- @config.clamp
78
-
79
- @log_writer.formatter = LogWriter::PidFormatter.new if clustered?
80
- @log_writer.formatter = options[:log_formatter] if @options[:log_formatter]
81
-
82
- @log_writer.custom_logger = options[:custom_logger] if @options[:custom_logger]
83
-
84
- generate_restart_data
85
-
86
98
  if clustered? && !Puma.forkable?
87
99
  unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
88
100
  end
89
101
 
90
- Dir.chdir(@restart_dir)
91
-
92
- prune_bundler!
93
-
94
102
  @environment = @options[:environment] if @options[:environment]
95
103
  set_rack_environment
96
104
 
97
105
  if clustered?
98
- @options[:logger] = @log_writer
99
-
100
106
  @runner = Cluster.new(self)
101
107
  else
102
108
  @runner = Single.new(self)
@@ -104,8 +110,6 @@ module Puma
104
110
  Puma.stats_object = @runner
105
111
 
106
112
  @status = :run
107
-
108
- log_config if ENV['PUMA_LOG_CONFIG']
109
113
  end
110
114
 
111
115
  attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir
@@ -138,7 +142,10 @@ module Puma
138
142
  # Delete the configured pidfile
139
143
  def delete_pidfile
140
144
  path = @options[:pidfile]
141
- File.unlink(path) if path && File.exist?(path)
145
+ begin
146
+ File.unlink(path) if path
147
+ rescue Errno::ENOENT
148
+ end
142
149
  end
143
150
 
144
151
  # Begin async shutdown of the server
@@ -165,6 +172,13 @@ module Puma
165
172
  log "* phased-restart called but not available, restarting normally."
166
173
  return restart
167
174
  end
175
+
176
+ if @options.file_options[:tag].nil?
177
+ dir = File.realdirpath(@restart_dir)
178
+ @options[:tag] = File.basename(dir)
179
+ set_process_title
180
+ end
181
+
168
182
  true
169
183
  end
170
184
 
@@ -268,7 +282,7 @@ module Puma
268
282
  end
269
283
 
270
284
  def do_graceful_stop
271
- @events.fire_on_stopped!
285
+ @events.fire_after_stopped!
272
286
  @runner.stop_blocked
273
287
  end
274
288
 
@@ -280,14 +294,16 @@ module Puma
280
294
  end
281
295
 
282
296
  def restart!
283
- @events.fire_on_restart!
284
- @config.run_hooks :on_restart, self, @log_writer
297
+ @events.fire_before_restart!
298
+ @config.run_hooks :before_restart, self, @log_writer
285
299
 
286
300
  if Puma.jruby?
287
301
  close_binder_listeners
288
302
 
289
303
  require_relative 'jruby_restart'
290
- JRubyRestart.chdir_exec(@restart_dir, restart_args)
304
+ argv = restart_args
305
+ JRubyRestart.chdir(@restart_dir)
306
+ Kernel.exec(*argv)
291
307
  elsif Puma.windows?
292
308
  close_binder_listeners
293
309
 
@@ -371,9 +387,9 @@ module Puma
371
387
  # using it.
372
388
  @restart_dir = Dir.pwd
373
389
 
374
- # Use the same trick as unicorn, namely favor PWD because
375
- # it will contain an unresolved symlink, useful for when
376
- # the pwd is /data/releases/current.
390
+ # Use the same trick as unicorn, namely favor PWD because
391
+ # it will contain an unresolved symlink, useful for when
392
+ # the pwd is /data/releases/current.
377
393
  elsif dir = ENV['PWD']
378
394
  s_env = File.stat(dir)
379
395
  s_pwd = File.stat(Dir.pwd)
@@ -408,12 +424,14 @@ module Puma
408
424
  end
409
425
 
410
426
  def setup_signals
411
- begin
412
- Signal.trap "SIGUSR2" do
413
- restart
427
+ unless ENV["PUMA_SKIP_SIGUSR2"]
428
+ begin
429
+ Signal.trap "SIGUSR2" do
430
+ restart
431
+ end
432
+ rescue Exception
433
+ log "*** SIGUSR2 not implemented, signal based restart unavailable!"
414
434
  end
415
- rescue Exception
416
- log "*** SIGUSR2 not implemented, signal based restart unavailable!"
417
435
  end
418
436
 
419
437
  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+
@@ -57,6 +57,7 @@ module Puma
57
57
 
58
58
  ctx.ca = params['ca'] if params['ca']
59
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
60
61
 
61
62
  ctx.reuse = params['reuse'] if params['reuse']
62
63
  end
data/lib/puma/minissl.rb CHANGED
@@ -172,7 +172,6 @@ module Puma
172
172
  end
173
173
  end
174
174
  rescue IOError, SystemCallError
175
- Puma::Util.purge_interrupt_queue
176
175
  # nothing
177
176
  ensure
178
177
  @socket.close
@@ -289,6 +288,7 @@ module Puma
289
288
  attr_reader :cert_pem
290
289
  attr_reader :key_pem
291
290
  attr_accessor :ssl_cipher_filter
291
+ attr_accessor :ssl_ciphersuites
292
292
  attr_accessor :verification_flags
293
293
 
294
294
  attr_reader :reuse, :reuse_cache_size, :reuse_timeout
data/lib/puma/null_io.rb CHANGED
@@ -16,6 +16,10 @@ 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
25
  def read(length = nil, buffer = nil)
@@ -39,6 +43,11 @@ module Puma
39
43
  def rewind
40
44
  end
41
45
 
46
+ def seek(pos, whence = 0)
47
+ raise ArgumentError, "negative length #{pos} given" if pos.negative?
48
+ 0
49
+ end
50
+
42
51
  def close
43
52
  end
44
53
 
@@ -71,5 +80,22 @@ module Puma
71
80
  def closed?
72
81
  false
73
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
74
100
  end
75
101
  end
@@ -14,9 +14,9 @@ Puma::Plugin.create do
14
14
  launcher.log_writer.log "* Enabling systemd notification integration"
15
15
 
16
16
  # hook_events
17
- launcher.events.on_booted { Puma::SdNotify.ready }
18
- launcher.events.on_stopped { Puma::SdNotify.stopping }
19
- launcher.events.on_restart { Puma::SdNotify.reloading }
17
+ launcher.events.after_booted { Puma::SdNotify.ready }
18
+ launcher.events.after_stopped { Puma::SdNotify.stopping }
19
+ launcher.events.before_restart { Puma::SdNotify.reloading }
20
20
 
21
21
  # start watchdog
22
22
  if Puma::SdNotify.watchdog?
@@ -70,7 +70,7 @@ module Puma::Rack
70
70
  return app.call(env)
71
71
  end
72
72
 
73
- [404, {'Content-Type' => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
73
+ [404, {'content-type' => "text/plain", "x-cascade" => "pass"}, ["Not Found: #{path}"]]
74
74
 
75
75
  ensure
76
76
  env['PATH_INFO'] = path
data/lib/puma/reactor.rb CHANGED
@@ -15,6 +15,12 @@ module Puma
15
15
  #
16
16
  # The implementation uses a Queue to synchronize adding new objects from the internal select loop.
17
17
  class Reactor
18
+
19
+ # @!attribute [rw] reactor_max
20
+ # Maximum number of clients in the selector. Reset with calls to `Server.stats`.
21
+ attr_accessor :reactor_max
22
+ attr_reader :reactor_size
23
+
18
24
  # Create a new Reactor to monitor IO objects added by #add.
19
25
  # The provided block will be invoked when an IO has data available to read,
20
26
  # its timeout elapses, or when the Reactor shuts down.
@@ -29,6 +35,8 @@ module Puma
29
35
  @input = Queue.new
30
36
  @timeouts = []
31
37
  @block = block
38
+ @reactor_size = 0
39
+ @reactor_max = 0
32
40
  end
33
41
 
34
42
  # Run the internal select loop, using a background thread by default.
@@ -67,17 +75,18 @@ module Puma
67
75
  private
68
76
 
69
77
  def select_loop
70
- close_selector = true
71
78
  begin
72
79
  until @input.closed? && @input.empty?
73
80
  # Wakeup any registered object that receives incoming data.
74
81
  # Block until the earliest timeout or Selector#wakeup is called.
75
82
  timeout = (earliest = @timeouts.first) && earliest.timeout
76
- @selector.select(timeout) {|mon| wakeup!(mon.value)}
83
+ @selector.select(timeout) do |monitor|
84
+ wakeup!(monitor.value)
85
+ end
77
86
 
78
87
  # Wakeup all objects that timed out.
79
- timed_out = @timeouts.take_while {|t| t.timeout == 0}
80
- timed_out.each { |c| wakeup! c }
88
+ timed_out = @timeouts.take_while { |client| client.timeout == 0 }
89
+ timed_out.each { |client| wakeup!(client) }
81
90
 
82
91
  unless @input.empty?
83
92
  until @input.empty?
@@ -91,23 +100,19 @@ module Puma
91
100
  STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
92
101
  STDERR.puts e.backtrace
93
102
 
94
- # NoMethodError may be rarely raised when calling @selector.select, which
95
- # is odd. Regardless, it may continue for thousands of calls if retried.
96
- # Also, when it raises, @selector.close also raises an error.
97
- if NoMethodError === e
98
- close_selector = false
99
- else
100
- retry
101
- end
103
+ retry
102
104
  end
105
+
103
106
  # Wakeup all remaining objects on shutdown.
104
107
  @timeouts.each(&@block)
105
- @selector.close if close_selector
108
+ @selector.close
106
109
  end
107
110
 
108
111
  # Start monitoring the object.
109
112
  def register(client)
110
113
  @selector.register(client.to_io, :r).value = client
114
+ @reactor_size += 1
115
+ @reactor_max = @reactor_size if @reactor_max < @reactor_size
111
116
  @timeouts << client
112
117
  rescue ArgumentError
113
118
  # unreadable clients raise error when processed by NIO
@@ -118,6 +123,7 @@ module Puma
118
123
  def wakeup!(client)
119
124
  if @block.call client
120
125
  @selector.deregister client.to_io
126
+ @reactor_size -= 1
121
127
  @timeouts.delete client
122
128
  end
123
129
  end