puma 6.1.1 → 6.3.1

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +76 -1
  3. data/LICENSE +0 -0
  4. data/README.md +39 -4
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +0 -0
  7. data/docs/compile_options.md +0 -0
  8. data/docs/deployment.md +0 -0
  9. data/docs/fork_worker.md +0 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +0 -0
  14. data/docs/jungle/rc.d/README.md +0 -0
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +0 -0
  17. data/docs/nginx.md +0 -0
  18. data/docs/plugins.md +0 -0
  19. data/docs/rails_dev_mode.md +0 -0
  20. data/docs/restart.md +0 -0
  21. data/docs/signals.md +0 -0
  22. data/docs/stats.md +0 -0
  23. data/docs/systemd.md +0 -0
  24. data/docs/testing_benchmarks_local_files.md +0 -0
  25. data/docs/testing_test_rackup_ci_files.md +0 -0
  26. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  27. data/ext/puma_http11/ext_help.h +0 -0
  28. data/ext/puma_http11/extconf.rb +0 -0
  29. data/ext/puma_http11/http11_parser.c +0 -0
  30. data/ext/puma_http11/http11_parser.h +0 -0
  31. data/ext/puma_http11/http11_parser.java.rl +0 -0
  32. data/ext/puma_http11/http11_parser.rl +0 -0
  33. data/ext/puma_http11/http11_parser_common.rl +0 -0
  34. data/ext/puma_http11/mini_ssl.c +30 -2
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +0 -0
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +0 -0
  39. data/ext/puma_http11/puma_http11.c +0 -0
  40. data/lib/puma/app/status.rb +1 -1
  41. data/lib/puma/binder.rb +8 -6
  42. data/lib/puma/cli.rb +1 -1
  43. data/lib/puma/client.rb +18 -10
  44. data/lib/puma/cluster/worker.rb +0 -0
  45. data/lib/puma/cluster/worker_handle.rb +0 -0
  46. data/lib/puma/cluster.rb +0 -0
  47. data/lib/puma/commonlogger.rb +21 -14
  48. data/lib/puma/configuration.rb +1 -0
  49. data/lib/puma/const.rb +58 -9
  50. data/lib/puma/control_cli.rb +0 -0
  51. data/lib/puma/detect.rb +0 -0
  52. data/lib/puma/dsl.rb +78 -2
  53. data/lib/puma/error_logger.rb +2 -1
  54. data/lib/puma/events.rb +0 -0
  55. data/lib/puma/io_buffer.rb +0 -0
  56. data/lib/puma/jruby_restart.rb +0 -0
  57. data/lib/puma/json_serialization.rb +0 -0
  58. data/lib/puma/launcher/bundle_pruner.rb +0 -0
  59. data/lib/puma/launcher.rb +2 -0
  60. data/lib/puma/log_writer.rb +10 -4
  61. data/lib/puma/minissl/context_builder.rb +1 -0
  62. data/lib/puma/minissl.rb +17 -0
  63. data/lib/puma/plugin/systemd.rb +0 -0
  64. data/lib/puma/plugin/tmp_restart.rb +0 -0
  65. data/lib/puma/plugin.rb +0 -0
  66. data/lib/puma/rack/builder.rb +2 -2
  67. data/lib/puma/rack/urlmap.rb +0 -0
  68. data/lib/puma/rack_default.rb +1 -1
  69. data/lib/puma/reactor.rb +16 -7
  70. data/lib/puma/request.rb +64 -48
  71. data/lib/puma/runner.rb +0 -0
  72. data/lib/puma/sd_notify.rb +0 -0
  73. data/lib/puma/server.rb +16 -4
  74. data/lib/puma/single.rb +0 -0
  75. data/lib/puma/state_file.rb +0 -0
  76. data/lib/puma/thread_pool.rb +7 -3
  77. data/lib/puma/util.rb +0 -0
  78. data/lib/puma.rb +0 -0
  79. data/lib/rack/handler/puma.rb +12 -6
  80. data/tools/Dockerfile +0 -0
  81. data/tools/trickletest.rb +0 -0
  82. metadata +2 -2
data/lib/puma/const.rb CHANGED
@@ -18,6 +18,7 @@ module Puma
18
18
  100 => 'Continue',
19
19
  101 => 'Switching Protocols',
20
20
  102 => 'Processing',
21
+ 103 => 'Early Hints',
21
22
  200 => 'OK',
22
23
  201 => 'Created',
23
24
  202 => 'Accepted',
@@ -49,16 +50,16 @@ module Puma
49
50
  410 => 'Gone',
50
51
  411 => 'Length Required',
51
52
  412 => 'Precondition Failed',
52
- 413 => 'Payload Too Large',
53
+ 413 => 'Content Too Large',
53
54
  414 => 'URI Too Long',
54
55
  415 => 'Unsupported Media Type',
55
56
  416 => 'Range Not Satisfiable',
56
57
  417 => 'Expectation Failed',
57
- 418 => 'I\'m A Teapot',
58
58
  421 => 'Misdirected Request',
59
- 422 => 'Unprocessable Entity',
59
+ 422 => 'Unprocessable Content',
60
60
  423 => 'Locked',
61
61
  424 => 'Failed Dependency',
62
+ 425 => 'Too Early',
62
63
  426 => 'Upgrade Required',
63
64
  428 => 'Precondition Required',
64
65
  429 => 'Too Many Requests',
@@ -73,7 +74,7 @@ module Puma
73
74
  506 => 'Variant Also Negotiates',
74
75
  507 => 'Insufficient Storage',
75
76
  508 => 'Loop Detected',
76
- 510 => 'Not Extended',
77
+ 510 => 'Not Extended (OBSOLETED)',
77
78
  511 => 'Network Authentication Required'
78
79
  }.freeze
79
80
 
@@ -99,8 +100,8 @@ module Puma
99
100
  # too taxing on performance.
100
101
  module Const
101
102
 
102
- PUMA_VERSION = VERSION = "6.1.1"
103
- CODE_NAME = "The Way Up"
103
+ PUMA_VERSION = VERSION = "6.3.1"
104
+ CODE_NAME = "Mugi No Toki Itaru"
104
105
 
105
106
  PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
106
107
 
@@ -124,15 +125,15 @@ module Puma
124
125
  # Indicate that we couldn't parse the request
125
126
  400 => "HTTP/1.1 400 Bad Request\r\n\r\n",
126
127
  # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
127
- 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND",
128
+ 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n",
128
129
  # The standard empty 408 response for requests that timed out.
129
- 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n",
130
+ 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n",
130
131
  # Indicate that there was an internal error, obviously.
131
132
  500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n",
132
133
  # Incorrect or invalid header value
133
134
  501 => "HTTP/1.1 501 Not Implemented\r\n\r\n",
134
135
  # A common header for indicating the server is too busy. Not used yet.
135
- 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY"
136
+ 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\n"
136
137
  }.freeze
137
138
 
138
139
  # The basic max request size we'll try to read.
@@ -147,7 +148,55 @@ module Puma
147
148
 
148
149
  REQUEST_METHOD = "REQUEST_METHOD"
149
150
  HEAD = "HEAD"
151
+
152
+ # based on https://www.rfc-editor.org/rfc/rfc9110.html#name-overview,
153
+ # with CONNECT removed, and PATCH added
150
154
  SUPPORTED_HTTP_METHODS = %w[HEAD GET POST PUT DELETE OPTIONS TRACE PATCH].freeze
155
+
156
+ # list from https://www.iana.org/assignments/http-methods/http-methods.xhtml
157
+ # as of 04-May-23
158
+ IANA_HTTP_METHODS = %w[
159
+ ACL
160
+ BASELINE-CONTROL
161
+ BIND
162
+ CHECKIN
163
+ CHECKOUT
164
+ CONNECT
165
+ COPY
166
+ DELETE
167
+ GET
168
+ HEAD
169
+ LABEL
170
+ LINK
171
+ LOCK
172
+ MERGE
173
+ MKACTIVITY
174
+ MKCALENDAR
175
+ MKCOL
176
+ MKREDIRECTREF
177
+ MKWORKSPACE
178
+ MOVE
179
+ OPTIONS
180
+ ORDERPATCH
181
+ PATCH
182
+ POST
183
+ PRI
184
+ PROPFIND
185
+ PROPPATCH
186
+ PUT
187
+ REBIND
188
+ REPORT
189
+ SEARCH
190
+ TRACE
191
+ UNBIND
192
+ UNCHECKOUT
193
+ UNLINK
194
+ UNLOCK
195
+ UPDATE
196
+ UPDATEREDIRECTREF
197
+ VERSION-CONTROL
198
+ ].freeze
199
+
151
200
  # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
152
201
  LINE_END = "\r\n"
153
202
  REMOTE_ADDR = "REMOTE_ADDR"
File without changes
data/lib/puma/detect.rb CHANGED
File without changes
data/lib/puma/dsl.rb CHANGED
@@ -89,6 +89,7 @@ module Puma
89
89
 
90
90
  cert_flags = (cert = opts[:cert]) ? "cert=#{Puma::Util.escape(cert)}" : nil
91
91
  key_flags = (key = opts[:key]) ? "&key=#{Puma::Util.escape(key)}" : nil
92
+ password_flags = (password_command = opts[:key_password_command]) ? "&key_password_command=#{Puma::Util.escape(password_command)}" : nil
92
93
 
93
94
  reuse_flag =
94
95
  if (reuse = opts[:reuse])
@@ -114,7 +115,7 @@ module Puma
114
115
  nil
115
116
  end
116
117
 
117
- "ssl://#{host}:#{port}?#{cert_flags}#{key_flags}#{ssl_cipher_filter}" \
118
+ "ssl://#{host}:#{port}?#{cert_flags}#{key_flags}#{password_flags}#{ssl_cipher_filter}" \
118
119
  "#{reuse_flag}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}#{low_latency_str}"
119
120
  end
120
121
  end
@@ -419,6 +420,11 @@ module Puma
419
420
  @options[:log_requests] = which
420
421
  end
421
422
 
423
+ # Pass in a custom logging class instance
424
+ def custom_logger(custom_logger)
425
+ @options[:custom_logger] = custom_logger
426
+ end
427
+
422
428
  # Show debugging info
423
429
  #
424
430
  def debug
@@ -585,6 +591,11 @@ module Puma
585
591
  @options[:silence_single_worker_warning] = true
586
592
  end
587
593
 
594
+ # Disable warning message when running single mode with callback hook defined.
595
+ def silence_fork_callback_warning
596
+ @options[:silence_fork_callback_warning] = true
597
+ end
598
+
588
599
  # Code to run immediately before master process
589
600
  # forks workers (once on boot). These hooks can block if necessary
590
601
  # to wait for background operations unknown to Puma to finish before
@@ -600,6 +611,8 @@ module Puma
600
611
  # puts "Starting workers..."
601
612
  # end
602
613
  def before_fork(&block)
614
+ warn_if_in_single_mode('before_fork')
615
+
603
616
  @options[:before_fork] ||= []
604
617
  @options[:before_fork] << block
605
618
  end
@@ -615,6 +628,8 @@ module Puma
615
628
  # puts 'Before worker boot...'
616
629
  # end
617
630
  def on_worker_boot(key = nil, &block)
631
+ warn_if_in_single_mode('on_worker_boot')
632
+
618
633
  process_hook :before_worker_boot, key, block, 'on_worker_boot'
619
634
  end
620
635
 
@@ -631,6 +646,8 @@ module Puma
631
646
  # puts 'On worker shutdown...'
632
647
  # end
633
648
  def on_worker_shutdown(key = nil, &block)
649
+ warn_if_in_single_mode('on_worker_shutdown')
650
+
634
651
  process_hook :before_worker_shutdown, key, block, 'on_worker_shutdown'
635
652
  end
636
653
 
@@ -645,6 +662,8 @@ module Puma
645
662
  # puts 'Before worker fork...'
646
663
  # end
647
664
  def on_worker_fork(&block)
665
+ warn_if_in_single_mode('on_worker_fork')
666
+
648
667
  process_hook :before_worker_fork, nil, block, 'on_worker_fork'
649
668
  end
650
669
 
@@ -659,11 +678,23 @@ module Puma
659
678
  # puts 'After worker fork...'
660
679
  # end
661
680
  def after_worker_fork(&block)
681
+ warn_if_in_single_mode('after_worker_fork')
682
+
662
683
  process_hook :after_worker_fork, nil, block, 'after_worker_fork'
663
684
  end
664
685
 
665
686
  alias_method :after_worker_boot, :after_worker_fork
666
687
 
688
+ # Code to run after puma is booted (works for both: single and clustered)
689
+ #
690
+ # @example
691
+ # on_booted do
692
+ # puts 'After booting...'
693
+ # end
694
+ def on_booted(&block)
695
+ @config.options[:events].on_booted(&block)
696
+ end
697
+
667
698
  # When `fork_worker` is enabled, code to run in Worker 0
668
699
  # before all other workers are re-forked from this process,
669
700
  # after the server has temporarily stopped serving requests
@@ -1036,6 +1067,38 @@ module Puma
1036
1067
  @options[:http_content_length_limit] = limit
1037
1068
  end
1038
1069
 
1070
+ # Supported http methods, which will replace `Puma::Const::SUPPORTED_HTTP_METHODS`.
1071
+ # The value of `:any` will allows all methods, otherwise, the value must be
1072
+ # an array of strings. Note that methods are all uppercase.
1073
+ #
1074
+ # `Puma::Const::SUPPORTED_HTTP_METHODS` is conservative, if you want a
1075
+ # complete set of methods, the methods defined by the
1076
+ # [IANA Method Registry](https://www.iana.org/assignments/http-methods/http-methods.xhtml)
1077
+ # are pre-defined as the constant `Puma::Const::IANA_HTTP_METHODS`.
1078
+ #
1079
+ # @note If the `methods` value is `:any`, no method check with be performed,
1080
+ # similar to Puma v5 and earlier.
1081
+ #
1082
+ # @example Adds 'PROPFIND' to existing supported methods
1083
+ # supported_http_methods(Puma::Const::SUPPORTED_HTTP_METHODS + ['PROPFIND'])
1084
+ # @example Restricts methods to the array elements
1085
+ # supported_http_methods %w[HEAD GET POST PUT DELETE OPTIONS PROPFIND]
1086
+ # @example Restricts methods to the methods in the IANA Registry
1087
+ # supported_http_methods Puma::Const::IANA_HTTP_METHODS
1088
+ # @example Allows any method
1089
+ # supported_http_methods :any
1090
+ #
1091
+ def supported_http_methods(methods)
1092
+ if methods == :any
1093
+ @options[:supported_http_methods] = :any
1094
+ elsif Array === methods && methods == (ary = methods.grep(String).uniq) &&
1095
+ !ary.empty?
1096
+ @options[:supported_http_methods] = ary
1097
+ else
1098
+ raise "supported_http_methods must be ':any' or a unique array of strings"
1099
+ end
1100
+ end
1101
+
1039
1102
  private
1040
1103
 
1041
1104
  # To avoid adding cert_pem and key_pem as URI params, we store them on the
@@ -1063,7 +1126,20 @@ module Puma
1063
1126
  elsif key.nil?
1064
1127
  @options[options_key] << block
1065
1128
  else
1066
- raise "'#{method}' key must be String or Symbol"
1129
+ raise "'#{meth}' key must be String or Symbol"
1130
+ end
1131
+ end
1132
+
1133
+ def warn_if_in_single_mode(hook_name)
1134
+ return if @options[:silence_fork_callback_warning]
1135
+
1136
+ if (@options[:workers] || 0) == 0
1137
+ log_string =
1138
+ "Warning: You specified code to run in a `#{hook_name}` block, " \
1139
+ "but Puma is not configured to run in cluster mode (worker count > 0 ), " \
1140
+ "so your `#{hook_name}` block did not run"
1141
+
1142
+ LogWriter.stdio.log(log_string)
1067
1143
  end
1068
1144
  end
1069
1145
  end
@@ -102,7 +102,8 @@ module Puma
102
102
  @ioerr.is_a?(IO) and @ioerr.wait_writable(1)
103
103
  @ioerr.write "#{w_str}\n"
104
104
  @ioerr.flush unless @ioerr.sync
105
- rescue Errno::EPIPE, Errno::EBADF, IOError
105
+ rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
106
+ # 'Invalid argument' (Errno::EINVAL) may be raised by flush
106
107
  end
107
108
  end
108
109
  rescue ThreadError
data/lib/puma/events.rb CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
data/lib/puma/launcher.rb CHANGED
@@ -79,6 +79,8 @@ module Puma
79
79
  @log_writer.formatter = LogWriter::PidFormatter.new if clustered?
80
80
  @log_writer.formatter = options[:log_formatter] if @options[:log_formatter]
81
81
 
82
+ @log_writer.custom_logger = options[:custom_logger] if @options[:custom_logger]
83
+
82
84
  generate_restart_data
83
85
 
84
86
  if clustered? && !Puma.forkable?
@@ -28,11 +28,12 @@ module Puma
28
28
  attr_reader :stdout,
29
29
  :stderr
30
30
 
31
- attr_accessor :formatter
31
+ attr_accessor :formatter, :custom_logger
32
32
 
33
33
  # Create a LogWriter that prints to +stdout+ and +stderr+.
34
34
  def initialize(stdout, stderr)
35
35
  @formatter = DefaultFormatter.new
36
+ @custom_logger = nil
36
37
  @stdout = stdout
37
38
  @stderr = stderr
38
39
 
@@ -59,7 +60,11 @@ module Puma
59
60
 
60
61
  # Write +str+ to +@stdout+
61
62
  def log(str)
62
- internal_write "#{@formatter.call str}\n"
63
+ if @custom_logger&.respond_to?(:write)
64
+ @custom_logger.write(format(str))
65
+ else
66
+ internal_write "#{@formatter.call str}\n"
67
+ end
63
68
  end
64
69
 
65
70
  def write(str)
@@ -73,7 +78,8 @@ module Puma
73
78
  @stdout.is_a?(IO) and @stdout.wait_writable(1)
74
79
  @stdout.write w_str
75
80
  @stdout.flush unless @stdout.sync
76
- rescue Errno::EPIPE, Errno::EBADF, IOError
81
+ rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
82
+ # 'Invalid argument' (Errno::EINVAL) may be raised by flush
77
83
  end
78
84
  end
79
85
  rescue ThreadError
@@ -119,7 +125,7 @@ module Puma
119
125
  def ssl_error(error, ssl_socket)
120
126
  peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
121
127
  peercert = ssl_socket.peercert
122
- subject = peercert ? peercert.subject : nil
128
+ subject = peercert&.subject
123
129
  @error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
124
130
  end
125
131
 
@@ -38,6 +38,7 @@ module Puma
38
38
 
39
39
  ctx.key = params['key'] if params['key']
40
40
  ctx.key_pem = params['key_pem'] if params['key_pem']
41
+ ctx.key_password_command = params['key_password_command'] if params['key_password_command']
41
42
 
42
43
  if params['cert'].nil? && params['cert_pem'].nil?
43
44
  log_writer.error "Please specify the SSL cert via 'cert=' or 'cert_pem='"
data/lib/puma/minissl.rb CHANGED
@@ -5,6 +5,7 @@ begin
5
5
  rescue LoadError
6
6
  end
7
7
 
8
+ require 'open3'
8
9
  # need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
9
10
  # use require, see https://github.com/puma/puma/pull/2381
10
11
  require 'puma/puma_http11'
@@ -277,6 +278,7 @@ module Puma
277
278
  else
278
279
  # non-jruby Context properties
279
280
  attr_reader :key
281
+ attr_reader :key_password_command
280
282
  attr_reader :cert
281
283
  attr_reader :ca
282
284
  attr_reader :cert_pem
@@ -291,6 +293,10 @@ module Puma
291
293
  @key = key
292
294
  end
293
295
 
296
+ def key_password_command=(key_password_command)
297
+ @key_password_command = key_password_command
298
+ end
299
+
294
300
  def cert=(cert)
295
301
  check_file cert, 'Cert'
296
302
  @cert = cert
@@ -316,6 +322,17 @@ module Puma
316
322
  raise "Cert not configured" if @cert.nil? && @cert_pem.nil?
317
323
  end
318
324
 
325
+ # Executes the command to return the password needed to decrypt the key.
326
+ def key_password
327
+ raise "Key password command not configured" if @key_password_command.nil?
328
+
329
+ stdout_str, stderr_str, status = Open3.capture3(@key_password_command)
330
+
331
+ return stdout_str.chomp if status.success?
332
+
333
+ raise "Key password failed with code #{status.exitstatus}: #{stderr_str}"
334
+ end
335
+
319
336
  # Controls session reuse. Allowed values are as follows:
320
337
  # * 'off' - matches the behavior of Puma 5.6 and earlier. This is included
321
338
  # in case reuse 'on' is made the default in future Puma versions.
File without changes
File without changes
data/lib/puma/plugin.rb CHANGED
File without changes
@@ -173,7 +173,7 @@ module Puma::Rack
173
173
  TOPLEVEL_BINDING, file, 0
174
174
  end
175
175
 
176
- def initialize(default_app = nil,&block)
176
+ def initialize(default_app = nil, &block)
177
177
  @use, @map, @run, @warmup = [], nil, default_app, nil
178
178
 
179
179
  # Conditionally load rack now, so that any rack middlewares,
@@ -183,7 +183,7 @@ module Puma::Rack
183
183
  rescue LoadError
184
184
  end
185
185
 
186
- instance_eval(&block) if block_given?
186
+ instance_eval(&block) if block
187
187
  end
188
188
 
189
189
  def self.app(default_app = nil, &block)
File without changes
@@ -11,7 +11,7 @@ if Object.const_defined? :Rackup
11
11
  end
12
12
  end
13
13
  end
14
- elsif Object.const_defined?(:Rack) && Rack::RELEASE < '3'
14
+ elsif Object.const_defined?(:Rack) && Rack.release < '3'
15
15
  module Rack
16
16
  module Handler
17
17
  def self.default(options = {})
data/lib/puma/reactor.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'queue_close' unless ::Queue.instance_methods.include? :close
4
-
5
3
  module Puma
6
4
  class UnsupportedBackend < StandardError; end
7
5
 
@@ -22,10 +20,12 @@ module Puma
22
20
  # its timeout elapses, or when the Reactor shuts down.
23
21
  def initialize(backend, &block)
24
22
  require 'nio'
25
- unless backend == :auto || NIO::Selector.backends.include?(backend)
26
- raise "unsupported IO selector backend: #{backend} (available backends: #{NIO::Selector.backends.join(', ')})"
23
+ valid_backends = [:auto, *::NIO::Selector.backends]
24
+ unless valid_backends.include?(backend)
25
+ raise ArgumentError.new("unsupported IO selector backend: #{backend} (available backends: #{valid_backends.join(', ')})")
27
26
  end
28
- @selector = backend == :auto ? NIO::Selector.new : NIO::Selector.new(backend)
27
+
28
+ @selector = ::NIO::Selector.new(NIO::Selector.backends.delete(backend))
29
29
  @input = Queue.new
30
30
  @timeouts = []
31
31
  @block = block
@@ -67,6 +67,7 @@ module Puma
67
67
  private
68
68
 
69
69
  def select_loop
70
+ close_selector = true
70
71
  begin
71
72
  until @input.closed? && @input.empty?
72
73
  # Wakeup any registered object that receives incoming data.
@@ -89,11 +90,19 @@ module Puma
89
90
  rescue StandardError => e
90
91
  STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
91
92
  STDERR.puts e.backtrace
92
- retry
93
+
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
93
102
  end
94
103
  # Wakeup all remaining objects on shutdown.
95
104
  @timeouts.each(&@block)
96
- @selector.close
105
+ @selector.close if close_selector
97
106
  end
98
107
 
99
108
  # Start monitoring the object.