puma 5.6.9-java → 6.6.0-java

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +465 -18
  3. data/README.md +152 -42
  4. data/bin/puma-wild +1 -1
  5. data/docs/compile_options.md +34 -0
  6. data/docs/fork_worker.md +12 -4
  7. data/docs/java_options.md +54 -0
  8. data/docs/kubernetes.md +12 -0
  9. data/docs/nginx.md +1 -1
  10. data/docs/plugins.md +4 -0
  11. data/docs/restart.md +1 -0
  12. data/docs/signals.md +2 -2
  13. data/docs/stats.md +8 -3
  14. data/docs/systemd.md +13 -7
  15. data/docs/testing_benchmarks_local_files.md +150 -0
  16. data/docs/testing_test_rackup_ci_files.md +36 -0
  17. data/ext/puma_http11/extconf.rb +27 -17
  18. data/ext/puma_http11/http11_parser.c +1 -1
  19. data/ext/puma_http11/http11_parser.h +1 -1
  20. data/ext/puma_http11/http11_parser.java.rl +2 -2
  21. data/ext/puma_http11/http11_parser.rl +2 -2
  22. data/ext/puma_http11/http11_parser_common.rl +2 -2
  23. data/ext/puma_http11/mini_ssl.c +137 -19
  24. data/ext/puma_http11/org/jruby/puma/Http11.java +31 -10
  25. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  26. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +157 -53
  27. data/ext/puma_http11/puma_http11.c +21 -10
  28. data/lib/puma/app/status.rb +4 -4
  29. data/lib/puma/binder.rb +60 -55
  30. data/lib/puma/cli.rb +22 -20
  31. data/lib/puma/client.rb +93 -30
  32. data/lib/puma/cluster/worker.rb +27 -17
  33. data/lib/puma/cluster/worker_handle.rb +8 -6
  34. data/lib/puma/cluster.rb +121 -47
  35. data/lib/puma/commonlogger.rb +21 -14
  36. data/lib/puma/configuration.rb +101 -65
  37. data/lib/puma/const.rb +141 -93
  38. data/lib/puma/control_cli.rb +19 -15
  39. data/lib/puma/detect.rb +7 -4
  40. data/lib/puma/dsl.rb +521 -88
  41. data/lib/puma/error_logger.rb +22 -13
  42. data/lib/puma/events.rb +6 -126
  43. data/lib/puma/io_buffer.rb +39 -4
  44. data/lib/puma/jruby_restart.rb +0 -15
  45. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  46. data/lib/puma/launcher.rb +121 -181
  47. data/lib/puma/log_writer.rb +147 -0
  48. data/lib/puma/minissl/context_builder.rb +27 -12
  49. data/lib/puma/minissl.rb +105 -11
  50. data/lib/puma/null_io.rb +42 -2
  51. data/lib/puma/plugin/systemd.rb +90 -0
  52. data/lib/puma/plugin/tmp_restart.rb +1 -1
  53. data/lib/puma/puma_http11.jar +0 -0
  54. data/lib/puma/rack/builder.rb +6 -6
  55. data/lib/puma/rack/urlmap.rb +1 -1
  56. data/lib/puma/rack_default.rb +19 -4
  57. data/lib/puma/reactor.rb +19 -10
  58. data/lib/puma/request.rb +368 -169
  59. data/lib/puma/runner.rb +65 -22
  60. data/lib/puma/sd_notify.rb +146 -0
  61. data/lib/puma/server.rb +161 -102
  62. data/lib/puma/single.rb +13 -11
  63. data/lib/puma/state_file.rb +3 -6
  64. data/lib/puma/thread_pool.rb +71 -21
  65. data/lib/puma/util.rb +1 -12
  66. data/lib/puma.rb +9 -10
  67. data/lib/rack/handler/puma.rb +116 -86
  68. data/tools/Dockerfile +2 -2
  69. metadata +17 -12
  70. data/lib/puma/queue_close.rb +0 -26
  71. data/lib/puma/systemd.rb +0 -46
  72. data/lib/rack/version_restriction.rb +0 -15
@@ -1,9 +1,9 @@
1
1
  module Puma
2
2
  module MiniSSL
3
3
  class ContextBuilder
4
- def initialize(params, events)
4
+ def initialize(params, log_writer)
5
5
  @params = params
6
- @events = events
6
+ @log_writer = log_writer
7
7
  end
8
8
 
9
9
  def context
@@ -11,27 +11,37 @@ module Puma
11
11
 
12
12
  if defined?(JRUBY_VERSION)
13
13
  unless params['keystore']
14
- events.error "Please specify the Java keystore via 'keystore='"
14
+ log_writer.error "Please specify the Java keystore via 'keystore='"
15
15
  end
16
16
 
17
17
  ctx.keystore = params['keystore']
18
18
 
19
19
  unless params['keystore-pass']
20
- events.error "Please specify the Java keystore password via 'keystore-pass='"
20
+ log_writer.error "Please specify the Java keystore password via 'keystore-pass='"
21
21
  end
22
22
 
23
23
  ctx.keystore_pass = params['keystore-pass']
24
- ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
24
+ ctx.keystore_type = params['keystore-type']
25
+
26
+ if truststore = params['truststore']
27
+ ctx.truststore = truststore.eql?('default') ? :default : truststore
28
+ ctx.truststore_pass = params['truststore-pass']
29
+ ctx.truststore_type = params['truststore-type']
30
+ end
31
+
32
+ ctx.cipher_suites = params['cipher_suites'] || params['ssl_cipher_list']
33
+ ctx.protocols = params['protocols'] if params['protocols']
25
34
  else
26
35
  if params['key'].nil? && params['key_pem'].nil?
27
- events.error "Please specify the SSL key via 'key=' or 'key_pem='"
36
+ log_writer.error "Please specify the SSL key via 'key=' or 'key_pem='"
28
37
  end
29
38
 
30
39
  ctx.key = params['key'] if params['key']
31
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']
32
42
 
33
43
  if params['cert'].nil? && params['cert_pem'].nil?
34
- events.error "Please specify the SSL cert via 'cert=' or 'cert_pem='"
44
+ log_writer.error "Please specify the SSL cert via 'cert=' or 'cert_pem='"
35
45
  end
36
46
 
37
47
  ctx.cert = params['cert'] if params['cert']
@@ -39,16 +49,21 @@ module Puma
39
49
 
40
50
  if ['peer', 'force_peer'].include?(params['verify_mode'])
41
51
  unless params['ca']
42
- events.error "Please specify the SSL ca via 'ca='"
52
+ log_writer.error "Please specify the SSL ca via 'ca='"
43
53
  end
54
+ # needed for Puma::MiniSSL::Socket#peercert, env['puma.peercert']
55
+ require 'openssl'
44
56
  end
45
57
 
46
58
  ctx.ca = params['ca'] if params['ca']
47
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
61
+
62
+ ctx.reuse = params['reuse'] if params['reuse']
48
63
  end
49
64
 
50
- ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
51
- ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
65
+ ctx.no_tlsv1 = params['no_tlsv1'] == 'true'
66
+ ctx.no_tlsv1_1 = params['no_tlsv1_1'] == 'true'
52
67
 
53
68
  if params['verify_mode']
54
69
  ctx.verify_mode = case params['verify_mode']
@@ -59,7 +74,7 @@ module Puma
59
74
  when "none"
60
75
  MiniSSL::VERIFY_NONE
61
76
  else
62
- events.error "Please specify a valid verify_mode="
77
+ log_writer.error "Please specify a valid verify_mode="
63
78
  MiniSSL::VERIFY_NONE
64
79
  end
65
80
  end
@@ -75,7 +90,7 @@ module Puma
75
90
 
76
91
  private
77
92
 
78
- attr_reader :params, :events
93
+ attr_reader :params, :log_writer
79
94
  end
80
95
  end
81
96
  end
data/lib/puma/minissl.rb CHANGED
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  begin
4
- require 'io/wait'
4
+ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
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`
10
+ # use require, see https://github.com/puma/puma/pull/2381
9
11
  require 'puma/puma_http11'
10
12
 
11
13
  module Puma
@@ -13,15 +15,16 @@ module Puma
13
15
  # Define constant at runtime, as it's easy to determine at built time,
14
16
  # but Puma could (it shouldn't) be loaded with an older OpenSSL version
15
17
  # @version 5.0.0
16
- HAS_TLS1_3 = !IS_JRUBY &&
17
- (OPENSSL_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) != -1 &&
18
- (OPENSSL_LIBRARY_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) !=-1
18
+ HAS_TLS1_3 = IS_JRUBY ||
19
+ ((OPENSSL_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) != -1 &&
20
+ (OPENSSL_LIBRARY_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) !=-1)
19
21
 
20
22
  class Socket
21
23
  def initialize(socket, engine)
22
24
  @socket = socket
23
25
  @engine = engine
24
26
  @peercert = nil
27
+ @reuse = nil
25
28
  end
26
29
 
27
30
  # @!attribute [r] to_io
@@ -50,7 +53,7 @@ module Puma
50
53
  # is made with TLSv1.3 as an available protocol
51
54
  # @version 5.0.0
52
55
  def bad_tlsv1_3?
53
- HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
56
+ HAS_TLS1_3 && ssl_version_state == ['TLSv1.3', 'SSLERR']
54
57
  end
55
58
  private :bad_tlsv1_3?
56
59
 
@@ -123,7 +126,7 @@ module Puma
123
126
  while true
124
127
  wrote = @engine.write data
125
128
 
126
- enc_wr = ''.dup
129
+ enc_wr = +''
127
130
  while (enc = @engine.extract)
128
131
  enc_wr << enc
129
132
  end
@@ -181,6 +184,11 @@ module Puma
181
184
  @socket.peeraddr
182
185
  end
183
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]
184
192
  # @!attribute [r] peercert
185
193
  def peercert
186
194
  return @peercert if @peercert
@@ -195,10 +203,6 @@ module Puma
195
203
  if IS_JRUBY
196
204
  OPENSSL_NO_SSL3 = false
197
205
  OPENSSL_NO_TLS1 = false
198
-
199
- class SSLError < StandardError
200
- # Define this for jruby even though it isn't used.
201
- end
202
206
  end
203
207
 
204
208
  class Context
@@ -212,6 +216,9 @@ module Puma
212
216
  @cert = nil
213
217
  @key_pem = nil
214
218
  @cert_pem = nil
219
+ @reuse = nil
220
+ @reuse_cache_size = nil
221
+ @reuse_timeout = nil
215
222
  end
216
223
 
217
224
  def check_file(file, desc)
@@ -222,33 +229,80 @@ module Puma
222
229
  if IS_JRUBY
223
230
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
224
231
  attr_reader :keystore
232
+ attr_reader :keystore_type
225
233
  attr_accessor :keystore_pass
226
- attr_accessor :ssl_cipher_list
234
+ attr_reader :truststore
235
+ attr_reader :truststore_type
236
+ attr_accessor :truststore_pass
237
+ attr_reader :cipher_suites
238
+ attr_reader :protocols
227
239
 
228
240
  def keystore=(keystore)
229
241
  check_file keystore, 'Keystore'
230
242
  @keystore = keystore
231
243
  end
232
244
 
245
+ def truststore=(truststore)
246
+ # NOTE: historically truststore was assumed the same as keystore, this is kept for backwards
247
+ # compatibility, to rely on JVM's trust defaults we allow setting `truststore = :default`
248
+ unless truststore.eql?(:default)
249
+ raise ArgumentError, "No such truststore file '#{truststore}'" unless File.exist?(truststore)
250
+ end
251
+ @truststore = truststore
252
+ end
253
+
254
+ def keystore_type=(type)
255
+ raise ArgumentError, "Invalid keystore type: #{type.inspect}" unless ['pkcs12', 'jks', nil].include?(type)
256
+ @keystore_type = type
257
+ end
258
+
259
+ def truststore_type=(type)
260
+ raise ArgumentError, "Invalid truststore type: #{type.inspect}" unless ['pkcs12', 'jks', nil].include?(type)
261
+ @truststore_type = type
262
+ end
263
+
264
+ def cipher_suites=(list)
265
+ list = list.split(',').map(&:strip) if list.is_a?(String)
266
+ @cipher_suites = list
267
+ end
268
+
269
+ # aliases for backwards compatibility
270
+ alias_method :ssl_cipher_list, :cipher_suites
271
+ alias_method :ssl_cipher_list=, :cipher_suites=
272
+
273
+ def protocols=(list)
274
+ list = list.split(',').map(&:strip) if list.is_a?(String)
275
+ @protocols = list
276
+ end
277
+
233
278
  def check
234
279
  raise "Keystore not configured" unless @keystore
280
+ # @truststore defaults to @keystore due backwards compatibility
235
281
  end
236
282
 
237
283
  else
238
284
  # non-jruby Context properties
239
285
  attr_reader :key
286
+ attr_reader :key_password_command
240
287
  attr_reader :cert
241
288
  attr_reader :ca
242
289
  attr_reader :cert_pem
243
290
  attr_reader :key_pem
244
291
  attr_accessor :ssl_cipher_filter
292
+ attr_accessor :ssl_ciphersuites
245
293
  attr_accessor :verification_flags
246
294
 
295
+ attr_reader :reuse, :reuse_cache_size, :reuse_timeout
296
+
247
297
  def key=(key)
248
298
  check_file key, 'Key'
249
299
  @key = key
250
300
  end
251
301
 
302
+ def key_password_command=(key_password_command)
303
+ @key_password_command = key_password_command
304
+ end
305
+
252
306
  def cert=(cert)
253
307
  check_file cert, 'Cert'
254
308
  @cert = cert
@@ -273,6 +327,46 @@ module Puma
273
327
  raise "Key not configured" if @key.nil? && @key_pem.nil?
274
328
  raise "Cert not configured" if @cert.nil? && @cert_pem.nil?
275
329
  end
330
+
331
+ # Executes the command to return the password needed to decrypt the key.
332
+ def key_password
333
+ raise "Key password command not configured" if @key_password_command.nil?
334
+
335
+ stdout_str, stderr_str, status = Open3.capture3(@key_password_command)
336
+
337
+ return stdout_str.chomp if status.success?
338
+
339
+ raise "Key password failed with code #{status.exitstatus}: #{stderr_str}"
340
+ end
341
+
342
+ # Controls session reuse. Allowed values are as follows:
343
+ # * 'off' - matches the behavior of Puma 5.6 and earlier. This is included
344
+ # in case reuse 'on' is made the default in future Puma versions.
345
+ # * 'dflt' - sets session reuse on, with OpenSSL default cache size of
346
+ # 20k and default timeout of 300 seconds.
347
+ # * 's,t' - where s and t are integer strings, for size and timeout.
348
+ # * 's' - where s is an integer strings for size.
349
+ # * ',t' - where t is an integer strings for timeout.
350
+ #
351
+ def reuse=(reuse_str)
352
+ case reuse_str
353
+ when 'off'
354
+ @reuse = nil
355
+ when 'dflt'
356
+ @reuse = true
357
+ when /\A\d+\z/
358
+ @reuse = true
359
+ @reuse_cache_size = reuse_str.to_i
360
+ when /\A\d+,\d+\z/
361
+ @reuse = true
362
+ size, time = reuse_str.split ','
363
+ @reuse_cache_size = size.to_i
364
+ @reuse_timeout = time.to_i
365
+ when /\A,\d+\z/
366
+ @reuse = true
367
+ @reuse_timeout = reuse_str.delete(',').to_i
368
+ end
369
+ end
276
370
  end
277
371
 
278
372
  # disables TLSv1
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
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../plugin'
4
+
5
+ # Puma's systemd integration allows Puma to inform systemd:
6
+ # 1. when it has successfully started
7
+ # 2. when it is starting shutdown
8
+ # 3. periodically for a liveness check with a watchdog thread
9
+ # 4. periodically set the status
10
+ Puma::Plugin.create do
11
+ def start(launcher)
12
+ require_relative '../sd_notify'
13
+
14
+ launcher.log_writer.log "* Enabling systemd notification integration"
15
+
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 }
20
+
21
+ # start watchdog
22
+ if Puma::SdNotify.watchdog?
23
+ ping_f = watchdog_sleep_time
24
+
25
+ in_background do
26
+ launcher.log_writer.log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
27
+ loop do
28
+ sleep ping_f
29
+ Puma::SdNotify.watchdog
30
+ end
31
+ end
32
+ end
33
+
34
+ # start status loop
35
+ instance = self
36
+ sleep_time = 1.0
37
+ in_background do
38
+ launcher.log_writer.log "Sending status to systemd every #{sleep_time.round(1)} sec"
39
+
40
+ loop do
41
+ sleep sleep_time
42
+ # TODO: error handling?
43
+ Puma::SdNotify.status(instance.status)
44
+ end
45
+ end
46
+ end
47
+
48
+ def status
49
+ if clustered?
50
+ messages = stats[:worker_status].map do |worker|
51
+ common_message(worker[:last_status])
52
+ end.join(',')
53
+
54
+ "Puma #{Puma::Const::VERSION}: cluster: #{booted_workers}/#{workers}, worker_status: [#{messages}]"
55
+ else
56
+ "Puma #{Puma::Const::VERSION}: worker: #{common_message(stats)}"
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def watchdog_sleep_time
63
+ usec = Integer(ENV["WATCHDOG_USEC"])
64
+
65
+ sec_f = usec / 1_000_000.0
66
+ # "It is recommended that a daemon sends a keep-alive notification message
67
+ # to the service manager every half of the time returned here."
68
+ sec_f / 2
69
+ end
70
+
71
+ def stats
72
+ Puma.stats_hash
73
+ end
74
+
75
+ def clustered?
76
+ stats.has_key?(:workers)
77
+ end
78
+
79
+ def workers
80
+ stats.fetch(:workers, 1)
81
+ end
82
+
83
+ def booted_workers
84
+ stats.fetch(:booted_workers, 1)
85
+ end
86
+
87
+ def common_message(stats)
88
+ "{ #{stats[:running]}/#{stats[:max_threads]} threads, #{stats[:pool_capacity]} available, #{stats[:backlog]} backlog }"
89
+ end
90
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/plugin'
3
+ require_relative '../plugin'
4
4
 
5
5
  Puma::Plugin.create do
6
6
  def start(launcher)
Binary file
@@ -102,13 +102,13 @@ module Puma::Rack
102
102
  begin
103
103
  info = []
104
104
  server = Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
105
- if server && server.respond_to?(:valid_options)
105
+ if server&.respond_to?(:valid_options)
106
106
  info << ""
107
107
  info << "Server-specific options for #{server.name}:"
108
108
 
109
109
  has_options = false
110
110
  server.valid_options.each do |name, description|
111
- next if name.to_s =~ /^(Host|Port)[^a-zA-Z]/ # ignore handler's host and port options, we do our own.
111
+ next if /^(Host|Port)[^a-zA-Z]/.match? name.to_s # ignore handler's host and port options, we do our own.
112
112
 
113
113
  info << " -O %-21s %s" % [name, description]
114
114
  has_options = true
@@ -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)
@@ -276,7 +276,7 @@ module Puma::Rack
276
276
  app = @map ? generate_map(@run, @map) : @run
277
277
  fail "missing run or map statement" unless app
278
278
  app = @use.reverse.inject(app) { |a,e| e[a] }
279
- @warmup.call(app) if @warmup
279
+ @warmup&.call app
280
280
  app
281
281
  end
282
282
 
@@ -287,7 +287,7 @@ module Puma::Rack
287
287
  private
288
288
 
289
289
  def generate_map(default_app, mapping)
290
- require 'puma/rack/urlmap'
290
+ require_relative 'urlmap'
291
291
 
292
292
  mapped = default_app ? {'/' => default_app} : {}
293
293
  mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
@@ -34,7 +34,7 @@ module Puma::Rack
34
34
  end
35
35
 
36
36
  location = location.chomp('/')
37
- match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
37
+ match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", Regexp::NOENCODING)
38
38
 
39
39
  [host, location, match, app]
40
40
  }.sort_by do |(host, location, _, _)|
@@ -1,9 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/handler/puma'
3
+ require_relative '../rack/handler/puma'
4
4
 
5
- module Rack::Handler
6
- def self.default(options = {})
7
- Rack::Handler::Puma
5
+ # rackup was removed in Rack 3, it is now a separate gem
6
+ if Object.const_defined? :Rackup
7
+ module Rackup
8
+ module Handler
9
+ def self.default(options = {})
10
+ ::Rackup::Handler::Puma
11
+ end
12
+ end
8
13
  end
14
+ elsif Object.const_defined?(:Rack) && Rack.release < '3'
15
+ module Rack
16
+ module Handler
17
+ def self.default(options = {})
18
+ ::Rack::Handler::Puma
19
+ end
20
+ end
21
+ end
22
+ else
23
+ raise "Rack 3 must be used with the Rackup gem"
9
24
  end
data/lib/puma/reactor.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/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
@@ -50,7 +50,7 @@ module Puma
50
50
  @input << client
51
51
  @selector.wakeup
52
52
  true
53
- rescue ClosedQueueError
53
+ rescue ClosedQueueError, IOError # Ignore if selector is already closed
54
54
  false
55
55
  end
56
56
 
@@ -61,12 +61,13 @@ module Puma
61
61
  @selector.wakeup
62
62
  rescue IOError # Ignore if selector is already closed
63
63
  end
64
- @thread.join if @thread
64
+ @thread&.join
65
65
  end
66
66
 
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.
@@ -76,7 +77,7 @@ module Puma
76
77
 
77
78
  # Wakeup all objects that timed out.
78
79
  timed_out = @timeouts.take_while {|t| t.timeout == 0}
79
- timed_out.each(&method(:wakeup!))
80
+ timed_out.each { |c| wakeup! c }
80
81
 
81
82
  unless @input.empty?
82
83
  until @input.empty?
@@ -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.