puma 5.6.8 → 6.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +332 -16
- data/README.md +79 -29
- data/bin/puma-wild +1 -1
- data/docs/compile_options.md +34 -0
- data/docs/fork_worker.md +1 -3
- data/docs/kubernetes.md +12 -0
- data/docs/nginx.md +1 -1
- data/docs/restart.md +1 -0
- data/docs/systemd.md +3 -6
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/extconf.rb +16 -9
- data/ext/puma_http11/http11_parser.c +1 -1
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +2 -2
- data/ext/puma_http11/http11_parser.rl +2 -2
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +127 -19
- data/ext/puma_http11/org/jruby/puma/Http11.java +5 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +157 -53
- data/ext/puma_http11/puma_http11.c +17 -9
- data/lib/puma/app/status.rb +4 -4
- data/lib/puma/binder.rb +50 -53
- data/lib/puma/cli.rb +16 -18
- data/lib/puma/client.rb +59 -19
- data/lib/puma/cluster/worker.rb +18 -11
- data/lib/puma/cluster/worker_handle.rb +4 -1
- data/lib/puma/cluster.rb +102 -40
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +77 -59
- data/lib/puma/const.rb +137 -92
- data/lib/puma/control_cli.rb +15 -11
- data/lib/puma/detect.rb +7 -4
- data/lib/puma/dsl.rb +250 -56
- data/lib/puma/error_logger.rb +18 -9
- data/lib/puma/events.rb +6 -126
- data/lib/puma/io_buffer.rb +39 -4
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +102 -175
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +26 -12
- data/lib/puma/minissl.rb +104 -11
- data/lib/puma/null_io.rb +16 -2
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/rack/builder.rb +6 -6
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +19 -10
- data/lib/puma/request.rb +380 -172
- data/lib/puma/runner.rb +56 -20
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +137 -89
- data/lib/puma/single.rb +13 -11
- data/lib/puma/state_file.rb +3 -6
- data/lib/puma/thread_pool.rb +57 -19
- data/lib/puma/util.rb +0 -11
- data/lib/puma.rb +9 -10
- data/lib/rack/handler/puma.rb +113 -86
- data/tools/Dockerfile +2 -2
- metadata +11 -7
- data/lib/puma/queue_close.rb +0 -26
- data/lib/puma/systemd.rb +0 -46
- data/lib/rack/version_restriction.rb +0 -15
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 =
|
17
|
-
|
18
|
-
|
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 &&
|
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 = ''
|
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,21 +229,61 @@ 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
|
-
|
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
|
@@ -244,11 +291,17 @@ module Puma
|
|
244
291
|
attr_accessor :ssl_cipher_filter
|
245
292
|
attr_accessor :verification_flags
|
246
293
|
|
294
|
+
attr_reader :reuse, :reuse_cache_size, :reuse_timeout
|
295
|
+
|
247
296
|
def key=(key)
|
248
297
|
check_file key, 'Key'
|
249
298
|
@key = key
|
250
299
|
end
|
251
300
|
|
301
|
+
def key_password_command=(key_password_command)
|
302
|
+
@key_password_command = key_password_command
|
303
|
+
end
|
304
|
+
|
252
305
|
def cert=(cert)
|
253
306
|
check_file cert, 'Cert'
|
254
307
|
@cert = cert
|
@@ -273,6 +326,46 @@ module Puma
|
|
273
326
|
raise "Key not configured" if @key.nil? && @key_pem.nil?
|
274
327
|
raise "Cert not configured" if @cert.nil? && @cert_pem.nil?
|
275
328
|
end
|
329
|
+
|
330
|
+
# Executes the command to return the password needed to decrypt the key.
|
331
|
+
def key_password
|
332
|
+
raise "Key password command not configured" if @key_password_command.nil?
|
333
|
+
|
334
|
+
stdout_str, stderr_str, status = Open3.capture3(@key_password_command)
|
335
|
+
|
336
|
+
return stdout_str.chomp if status.success?
|
337
|
+
|
338
|
+
raise "Key password failed with code #{status.exitstatus}: #{stderr_str}"
|
339
|
+
end
|
340
|
+
|
341
|
+
# Controls session reuse. Allowed values are as follows:
|
342
|
+
# * 'off' - matches the behavior of Puma 5.6 and earlier. This is included
|
343
|
+
# in case reuse 'on' is made the default in future Puma versions.
|
344
|
+
# * 'dflt' - sets session reuse on, with OpenSSL default cache size of
|
345
|
+
# 20k and default timeout of 300 seconds.
|
346
|
+
# * 's,t' - where s and t are integer strings, for size and timeout.
|
347
|
+
# * 's' - where s is an integer strings for size.
|
348
|
+
# * ',t' - where t is an integer strings for timeout.
|
349
|
+
#
|
350
|
+
def reuse=(reuse_str)
|
351
|
+
case reuse_str
|
352
|
+
when 'off'
|
353
|
+
@reuse = nil
|
354
|
+
when 'dflt'
|
355
|
+
@reuse = true
|
356
|
+
when /\A\d+\z/
|
357
|
+
@reuse = true
|
358
|
+
@reuse_cache_size = reuse_str.to_i
|
359
|
+
when /\A\d+,\d+\z/
|
360
|
+
@reuse = true
|
361
|
+
size, time = reuse_str.split ','
|
362
|
+
@reuse_cache_size = size.to_i
|
363
|
+
@reuse_timeout = time.to_i
|
364
|
+
when /\A,\d+\z/
|
365
|
+
@reuse = true
|
366
|
+
@reuse_timeout = reuse_str.delete(',').to_i
|
367
|
+
end
|
368
|
+
end
|
276
369
|
end
|
277
370
|
|
278
371
|
# disables TLSv1
|
data/lib/puma/null_io.rb
CHANGED
@@ -18,8 +18,22 @@ module Puma
|
|
18
18
|
|
19
19
|
# Mimics IO#read with no data.
|
20
20
|
#
|
21
|
-
def read(
|
22
|
-
|
21
|
+
def read(length = nil, buffer = nil)
|
22
|
+
if length.to_i < 0
|
23
|
+
raise ArgumentError, "(negative length #{length} given)"
|
24
|
+
end
|
25
|
+
|
26
|
+
buffer = if buffer.nil?
|
27
|
+
"".b
|
28
|
+
else
|
29
|
+
String.try_convert(buffer) or raise TypeError, "no implicit conversion of #{buffer.class} into String"
|
30
|
+
end
|
31
|
+
buffer.clear
|
32
|
+
if length.to_i > 0
|
33
|
+
nil
|
34
|
+
else
|
35
|
+
buffer
|
36
|
+
end
|
23
37
|
end
|
24
38
|
|
25
39
|
def rewind
|
@@ -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
|
data/lib/puma/rack/builder.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
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
|
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
|
-
|
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 }
|
data/lib/puma/rack/urlmap.rb
CHANGED
@@ -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('/', '/+')}(.*)",
|
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, _, _)|
|
data/lib/puma/rack_default.rb
CHANGED
@@ -1,9 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative '../rack/handler/puma'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
26
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
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.
|