puma 3.12.0 → 4.0.0
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.
- checksums.yaml +4 -4
- data/History.md +36 -0
- data/README.md +29 -9
- data/docs/architecture.md +1 -0
- data/docs/deployment.md +24 -4
- data/docs/restart.md +4 -2
- data/docs/systemd.md +27 -9
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/mini_ssl.c +32 -4
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +11 -4
- data/lib/puma/app/status.rb +3 -2
- data/lib/puma/binder.rb +19 -10
- data/lib/puma/cli.rb +2 -0
- data/lib/puma/client.rb +46 -25
- data/lib/puma/cluster.rb +40 -14
- data/lib/puma/commonlogger.rb +2 -0
- data/lib/puma/configuration.rb +4 -1
- data/lib/puma/const.rb +8 -2
- data/lib/puma/control_cli.rb +21 -9
- data/lib/puma/convenient.rb +2 -0
- data/lib/puma/daemon_ext.rb +2 -0
- data/lib/puma/delegation.rb +2 -0
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +57 -5
- data/lib/puma/events.rb +2 -0
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -0
- data/lib/puma/launcher.rb +14 -13
- data/lib/puma/minissl.rb +15 -1
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin.rb +2 -0
- data/lib/puma/rack/builder.rb +2 -1
- data/lib/puma/reactor.rb +106 -53
- data/lib/puma/runner.rb +3 -1
- data/lib/puma/server.rb +27 -24
- data/lib/puma/single.rb +4 -2
- data/lib/puma/state_file.rb +2 -0
- data/lib/puma/tcp_logger.rb +2 -0
- data/lib/puma/thread_pool.rb +7 -1
- data/lib/puma/util.rb +2 -6
- data/lib/rack/handler/puma.rb +3 -0
- data/tools/jungle/init.d/puma +5 -5
- metadata +19 -8
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
data/lib/puma/commonlogger.rb
CHANGED
data/lib/puma/configuration.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/rack/builder'
|
2
4
|
require 'puma/plugin'
|
3
5
|
require 'puma/const'
|
@@ -184,7 +186,8 @@ module Puma
|
|
184
186
|
:rackup => DefaultRackup,
|
185
187
|
:logger => STDOUT,
|
186
188
|
:persistent_timeout => Const::PERSISTENT_TIMEOUT,
|
187
|
-
:first_data_timeout => Const::FIRST_DATA_TIMEOUT
|
189
|
+
:first_data_timeout => Const::FIRST_DATA_TIMEOUT,
|
190
|
+
:raise_exception_on_sigterm => true
|
188
191
|
}
|
189
192
|
end
|
190
193
|
|
data/lib/puma/const.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
#encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
module Puma
|
3
5
|
class UnsupportedOption < RuntimeError
|
4
6
|
end
|
@@ -98,8 +100,8 @@ module Puma
|
|
98
100
|
# too taxing on performance.
|
99
101
|
module Const
|
100
102
|
|
101
|
-
PUMA_VERSION = VERSION = "
|
102
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "4.0.0".freeze
|
104
|
+
CODE_NAME = "4 Fast 4 Furious".freeze
|
103
105
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
104
106
|
|
105
107
|
FAST_TRACK_KA_TIMEOUT = 0.2
|
@@ -225,5 +227,9 @@ module Puma
|
|
225
227
|
HIJACK_IO = "rack.hijack_io".freeze
|
226
228
|
|
227
229
|
EARLY_HINTS = "rack.early_hints".freeze
|
230
|
+
|
231
|
+
# Mininum interval to checks worker health
|
232
|
+
WORKER_CHECK_INTERVAL = 5
|
233
|
+
|
228
234
|
end
|
229
235
|
end
|
data/lib/puma/control_cli.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'optparse'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
4
|
+
require_relative 'state_file'
|
5
|
+
require_relative 'const'
|
6
|
+
require_relative 'detect'
|
7
|
+
require_relative 'configuration'
|
6
8
|
require 'uri'
|
7
9
|
require 'socket'
|
8
10
|
|
@@ -129,7 +131,7 @@ module Puma
|
|
129
131
|
uri = URI.parse @control_url
|
130
132
|
|
131
133
|
# create server object by scheme
|
132
|
-
|
134
|
+
server = case uri.scheme
|
133
135
|
when "tcp"
|
134
136
|
TCPSocket.new uri.host, uri.port
|
135
137
|
when "unix"
|
@@ -147,9 +149,9 @@ module Puma
|
|
147
149
|
url = url + "?token=#{@control_auth_token}"
|
148
150
|
end
|
149
151
|
|
150
|
-
|
152
|
+
server << "GET #{url} HTTP/1.0\r\n\r\n"
|
151
153
|
|
152
|
-
unless data =
|
154
|
+
unless data = server.read
|
153
155
|
raise "Server closed connection before responding"
|
154
156
|
end
|
155
157
|
|
@@ -172,8 +174,8 @@ module Puma
|
|
172
174
|
message "Command #{@command} sent success"
|
173
175
|
message response.last if @command == "stats" || @command == "gc-stats"
|
174
176
|
end
|
175
|
-
|
176
|
-
|
177
|
+
ensure
|
178
|
+
server.close if server && !server.closed?
|
177
179
|
end
|
178
180
|
|
179
181
|
def send_signal
|
@@ -204,6 +206,16 @@ module Puma
|
|
204
206
|
when "phased-restart"
|
205
207
|
Process.kill "SIGUSR1", @pid
|
206
208
|
|
209
|
+
when "status"
|
210
|
+
begin
|
211
|
+
Process.kill 0, @pid
|
212
|
+
puts "Puma is started"
|
213
|
+
rescue Errno::ESRCH
|
214
|
+
raise "Puma is not running"
|
215
|
+
end
|
216
|
+
|
217
|
+
return
|
218
|
+
|
207
219
|
else
|
208
220
|
return
|
209
221
|
end
|
data/lib/puma/convenient.rb
CHANGED
data/lib/puma/daemon_ext.rb
CHANGED
data/lib/puma/delegation.rb
CHANGED
data/lib/puma/detect.rb
CHANGED
data/lib/puma/dsl.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'puma/const'
|
4
|
+
|
1
5
|
module Puma
|
2
6
|
# The methods that are available for use inside the config file.
|
3
7
|
# These same methods are used in Puma cli and the rack handler
|
@@ -55,6 +59,14 @@ module Puma
|
|
55
59
|
@plugins.clear
|
56
60
|
end
|
57
61
|
|
62
|
+
def set_default_host(host)
|
63
|
+
@options[:default_host] = host
|
64
|
+
end
|
65
|
+
|
66
|
+
def default_host
|
67
|
+
@options[:default_host] || Configuration::DefaultTCPHost
|
68
|
+
end
|
69
|
+
|
58
70
|
def inject(&blk)
|
59
71
|
instance_eval(&blk)
|
60
72
|
end
|
@@ -93,7 +105,12 @@ module Puma
|
|
93
105
|
end
|
94
106
|
|
95
107
|
if opts[:no_token]
|
96
|
-
|
108
|
+
# We need to use 'none' rather than :none because this value will be
|
109
|
+
# passed on to an instance of OptionParser, which doesn't support
|
110
|
+
# symbols as option values.
|
111
|
+
#
|
112
|
+
# See: https://github.com/puma/puma/issues/1193#issuecomment-305995488
|
113
|
+
auth_token = 'none'
|
97
114
|
else
|
98
115
|
auth_token = opts[:auth_token]
|
99
116
|
auth_token ||= Configuration.random_token
|
@@ -138,7 +155,7 @@ module Puma
|
|
138
155
|
# Define the TCP port to bind to. Use +bind+ for more advanced options.
|
139
156
|
#
|
140
157
|
def port(port, host=nil)
|
141
|
-
host ||=
|
158
|
+
host ||= default_host
|
142
159
|
bind "tcp://#{host}:#{port}"
|
143
160
|
end
|
144
161
|
|
@@ -285,12 +302,15 @@ module Puma
|
|
285
302
|
|
286
303
|
def ssl_bind(host, port, opts)
|
287
304
|
verify = opts.fetch(:verify_mode, 'none')
|
305
|
+
no_tlsv1 = opts.fetch(:no_tlsv1, 'false')
|
306
|
+
ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
|
288
307
|
|
289
308
|
if defined?(JRUBY_VERSION)
|
290
309
|
keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
|
291
|
-
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}"
|
310
|
+
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}#{ca_additions}"
|
292
311
|
else
|
293
|
-
|
312
|
+
ssl_cipher_filter = "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" if opts[:ssl_cipher_filter]
|
313
|
+
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}#{ssl_cipher_filter}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}#{ca_additions}"
|
294
314
|
end
|
295
315
|
end
|
296
316
|
|
@@ -365,6 +385,21 @@ module Puma
|
|
365
385
|
|
366
386
|
alias_method :after_worker_boot, :after_worker_fork
|
367
387
|
|
388
|
+
# Code to run out-of-band when the worker is idle.
|
389
|
+
# These hooks run immediately after a request has finished
|
390
|
+
# processing and there are no busy threads on the worker.
|
391
|
+
# The worker doesn't accept new requests until this code finishes.
|
392
|
+
#
|
393
|
+
# This hook is useful for running out-of-band garbage collection
|
394
|
+
# or scheduling asynchronous tasks to execute after a response.
|
395
|
+
#
|
396
|
+
# This can be called multiple times to add hooks.
|
397
|
+
#
|
398
|
+
def out_of_band(&block)
|
399
|
+
@options[:out_of_band] ||= []
|
400
|
+
@options[:out_of_band] << block
|
401
|
+
end
|
402
|
+
|
368
403
|
# The directory to operate out of.
|
369
404
|
def directory(dir)
|
370
405
|
@options[:directory] = dir.to_s
|
@@ -414,6 +449,16 @@ module Puma
|
|
414
449
|
@options[:prune_bundler] = answer
|
415
450
|
end
|
416
451
|
|
452
|
+
# In environments where SIGTERM is something expected, instructing
|
453
|
+
# puma to shutdown gracefully ( for example in Kubernetes, where
|
454
|
+
# rolling restart is guaranteed usually on infrastructure level )
|
455
|
+
# SignalException should not be raised for SIGTERM
|
456
|
+
#
|
457
|
+
# When set to false, if puma process receives SIGTERM, it won't raise SignalException
|
458
|
+
def raise_exception_on_sigterm(answer=true)
|
459
|
+
@options[:raise_exception_on_sigterm] = answer
|
460
|
+
end
|
461
|
+
|
417
462
|
# Additional text to display in process listing
|
418
463
|
def tag(string)
|
419
464
|
@options[:tag] = string.to_s
|
@@ -424,6 +469,13 @@ module Puma
|
|
424
469
|
# that have not checked in within the given +timeout+.
|
425
470
|
# This mitigates hung processes. Default value is 60 seconds.
|
426
471
|
def worker_timeout(timeout)
|
472
|
+
timeout = Integer(timeout)
|
473
|
+
min = Const::WORKER_CHECK_INTERVAL
|
474
|
+
|
475
|
+
if timeout <= min
|
476
|
+
raise "The minimum worker_timeout must be greater than the worker reporting interval (#{min})"
|
477
|
+
end
|
478
|
+
|
427
479
|
@options[:worker_timeout] = Integer(timeout)
|
428
480
|
end
|
429
481
|
|
@@ -493,7 +545,7 @@ module Puma
|
|
493
545
|
when Hash
|
494
546
|
if hdr = val[:header]
|
495
547
|
@options[:remote_address] = :header
|
496
|
-
@options[:remote_address_header] = "HTTP_" + hdr.upcase.
|
548
|
+
@options[:remote_address_header] = "HTTP_" + hdr.upcase.tr("-", "_")
|
497
549
|
else
|
498
550
|
raise "Invalid value for set_remote_address - #{val.inspect}"
|
499
551
|
end
|
data/lib/puma/events.rb
CHANGED
data/lib/puma/io_buffer.rb
CHANGED
data/lib/puma/jruby_restart.rb
CHANGED
data/lib/puma/launcher.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/events'
|
2
4
|
require 'puma/detect'
|
3
5
|
|
@@ -63,8 +65,8 @@ module Puma
|
|
63
65
|
|
64
66
|
generate_restart_data
|
65
67
|
|
66
|
-
if clustered? &&
|
67
|
-
unsupported
|
68
|
+
if clustered? && !Process.respond_to?(:fork)
|
69
|
+
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
|
68
70
|
end
|
69
71
|
|
70
72
|
if @options[:daemon] && Puma.windows?
|
@@ -212,6 +214,15 @@ module Puma
|
|
212
214
|
end
|
213
215
|
end
|
214
216
|
|
217
|
+
def close_binder_listeners
|
218
|
+
@binder.listeners.each do |l, io|
|
219
|
+
io.close
|
220
|
+
uri = URI.parse(l)
|
221
|
+
next unless uri.scheme == 'unix'
|
222
|
+
File.unlink("#{uri.host}#{uri.path}")
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
215
226
|
private
|
216
227
|
|
217
228
|
def reload_worker_directory
|
@@ -317,16 +328,6 @@ module Puma
|
|
317
328
|
@options[:prune_bundler] && clustered? && !@options[:preload_app]
|
318
329
|
end
|
319
330
|
|
320
|
-
def close_binder_listeners
|
321
|
-
@binder.listeners.each do |l, io|
|
322
|
-
io.close
|
323
|
-
uri = URI.parse(l)
|
324
|
-
next unless uri.scheme == 'unix'
|
325
|
-
File.unlink("#{uri.host}#{uri.path}")
|
326
|
-
end
|
327
|
-
end
|
328
|
-
|
329
|
-
|
330
331
|
def generate_restart_data
|
331
332
|
if dir = @options[:directory]
|
332
333
|
@restart_dir = dir
|
@@ -395,7 +396,7 @@ module Puma
|
|
395
396
|
Signal.trap "SIGTERM" do
|
396
397
|
graceful_stop
|
397
398
|
|
398
|
-
raise
|
399
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
399
400
|
end
|
400
401
|
rescue Exception
|
401
402
|
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
|
data/lib/puma/minissl.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
begin
|
2
4
|
require 'io/wait'
|
3
|
-
|
5
|
+
rescue LoadError
|
4
6
|
end
|
5
7
|
|
6
8
|
module Puma
|
@@ -175,6 +177,11 @@ module Puma
|
|
175
177
|
|
176
178
|
class Context
|
177
179
|
attr_accessor :verify_mode
|
180
|
+
attr_reader :no_tlsv1
|
181
|
+
|
182
|
+
def initialize
|
183
|
+
@no_tlsv1 = false
|
184
|
+
end
|
178
185
|
|
179
186
|
if defined?(JRUBY_VERSION)
|
180
187
|
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
@@ -213,11 +220,18 @@ module Puma
|
|
213
220
|
@ca = ca
|
214
221
|
end
|
215
222
|
|
223
|
+
|
216
224
|
def check
|
217
225
|
raise "Key not configured" unless @key
|
218
226
|
raise "Cert not configured" unless @cert
|
219
227
|
end
|
220
228
|
end
|
229
|
+
|
230
|
+
def no_tlsv1=(tlsv1)
|
231
|
+
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
|
232
|
+
@no_tlsv1 = tlsv1
|
233
|
+
end
|
234
|
+
|
221
235
|
end
|
222
236
|
|
223
237
|
VERIFY_NONE = 0
|
data/lib/puma/null_io.rb
CHANGED
data/lib/puma/plugin.rb
CHANGED
data/lib/puma/rack/builder.rb
CHANGED
@@ -110,7 +110,8 @@ module Puma::Rack
|
|
110
110
|
|
111
111
|
has_options = false
|
112
112
|
server.valid_options.each do |name, description|
|
113
|
-
next if name.to_s
|
113
|
+
next if name.to_s =~ /^(Host|Port)[^a-zA-Z]/ # ignore handler's host and port options, we do our own.
|
114
|
+
|
114
115
|
info << " -O %-21s %s" % [name, description]
|
115
116
|
has_options = true
|
116
117
|
end
|
data/lib/puma/reactor.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/util'
|
2
4
|
require 'puma/minissl'
|
3
5
|
|
6
|
+
require 'nio'
|
7
|
+
|
4
8
|
module Puma
|
5
9
|
# Internal Docs, Not a public interface.
|
6
10
|
#
|
@@ -16,12 +20,13 @@ module Puma
|
|
16
20
|
#
|
17
21
|
# ## Reactor Flow
|
18
22
|
#
|
19
|
-
# A
|
20
|
-
#
|
23
|
+
# A connection comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance,
|
24
|
+
# which stores it in an array and waits for any of the connections to be ready for reading.
|
21
25
|
#
|
22
|
-
#
|
26
|
+
# The waiting/wake up is performed with nio4r, which will use the apropriate backend (libev, Java NIO or
|
27
|
+
# just plain IO#select). The call to `NIO::Selector#select` will "wake up" and
|
23
28
|
# return the references to any objects that caused it to "wake". The reactor
|
24
|
-
# then loops through each of these request objects, and sees if they're
|
29
|
+
# then loops through each of these request objects, and sees if they're complete. If they
|
25
30
|
# have a full header and body then the reactor passes the request to a thread pool.
|
26
31
|
# Once in a thread pool, a "worker thread" can run the the application's Ruby code against the request.
|
27
32
|
#
|
@@ -36,7 +41,7 @@ module Puma
|
|
36
41
|
# Creates an instance of Puma::Reactor
|
37
42
|
#
|
38
43
|
# The `server` argument is an instance of `Puma::Server`
|
39
|
-
#
|
44
|
+
# that is used to write a response for "low level errors"
|
40
45
|
# when there is an exception inside of the reactor.
|
41
46
|
#
|
42
47
|
# The `app_pool` is an instance of `Puma::ThreadPool`.
|
@@ -47,6 +52,8 @@ module Puma
|
|
47
52
|
@events = server.events
|
48
53
|
@app_pool = app_pool
|
49
54
|
|
55
|
+
@selector = NIO::Selector.new
|
56
|
+
|
50
57
|
@mutex = Mutex.new
|
51
58
|
|
52
59
|
# Read / Write pipes to wake up internal while loop
|
@@ -55,24 +62,26 @@ module Puma
|
|
55
62
|
@sleep_for = DefaultSleepFor
|
56
63
|
@timeouts = []
|
57
64
|
|
58
|
-
|
65
|
+
mon = @selector.register(@ready, :r)
|
66
|
+
mon.value = @ready
|
67
|
+
|
68
|
+
@monitors = [mon]
|
59
69
|
end
|
60
70
|
|
61
71
|
private
|
62
72
|
|
63
|
-
|
64
73
|
# Until a request is added via the `add` method this method will internally
|
65
74
|
# loop, waiting on the `sockets` array objects. The only object in this
|
66
75
|
# array at first is the `@ready` IO object, which is the read end of a pipe
|
67
76
|
# connected to `@trigger` object. When `@trigger` is written to, then the loop
|
68
|
-
# will break on `
|
77
|
+
# will break on `NIO::Selector#select` and return an array.
|
69
78
|
#
|
70
79
|
# ## When a request is added:
|
71
80
|
#
|
72
81
|
# When the `add` method is called, an instance of `Puma::Client` is added to the `@input` array.
|
73
82
|
# Next the `@ready` pipe is "woken" by writing a string of `"*"` to `@trigger`.
|
74
83
|
#
|
75
|
-
# When that happens, the internal loop stops blocking at `
|
84
|
+
# When that happens, the internal loop stops blocking at `NIO::Selector#select` and returns a reference
|
76
85
|
# to whatever "woke" it up. On the very first loop, the only thing in `sockets` is `@ready`.
|
77
86
|
# When `@trigger` is written-to, the loop "wakes" and the `ready`
|
78
87
|
# variable returns an array of arrays that looks like `[[#<IO:fd 10>], [], []]` where the
|
@@ -88,11 +97,11 @@ module Puma
|
|
88
97
|
# to the `@ready` IO object. For example: `[#<IO:fd 10>, #<Puma::Client:0x3fdc1103bee8 @ready=false>]`.
|
89
98
|
#
|
90
99
|
# Since the `Puma::Client` in this example has data that has not been read yet,
|
91
|
-
# the `
|
100
|
+
# the `NIO::Selector#select` is immediately able to "wake" and read from the `Puma::Client`. At this point the
|
92
101
|
# `ready` output looks like this: `[[#<Puma::Client:0x3fdc1103bee8 @ready=false>], [], []]`.
|
93
102
|
#
|
94
103
|
# Each element in the first entry is iterated over. The `Puma::Client` object is not
|
95
|
-
# the `@ready` pipe, so the reactor checks to see if it has the
|
104
|
+
# the `@ready` pipe, so the reactor checks to see if it has the full header and body with
|
96
105
|
# the `Puma::Client#try_to_finish` method. If the full request has been sent,
|
97
106
|
# then the request is passed off to the `@app_pool` thread pool so that a "worker thread"
|
98
107
|
# can pick up the request and begin to execute application logic. This is done
|
@@ -100,56 +109,93 @@ module Puma
|
|
100
109
|
#
|
101
110
|
# If the request body is not present then nothing will happen, and the loop will iterate
|
102
111
|
# again. When the client sends more data to the socket the `Puma::Client` object will
|
103
|
-
# wake up the `
|
112
|
+
# wake up the `NIO::Selector#select` and it can again be checked to see if it's ready to be
|
104
113
|
# passed to the thread pool.
|
105
114
|
#
|
106
115
|
# ## Time Out Case
|
107
116
|
#
|
108
|
-
# In addition to being woken via a write to one of the sockets the `
|
117
|
+
# In addition to being woken via a write to one of the sockets the `NIO::Selector#select` will
|
109
118
|
# periodically "time out" of the sleep. One of the functions of this is to check for
|
110
119
|
# any requests that have "timed out". At the end of the loop it's checked to see if
|
111
|
-
# the first element in the `@timeout` array has exceed
|
112
|
-
# the client object is removed from the timeout
|
113
|
-
# Then
|
120
|
+
# the first element in the `@timeout` array has exceed its allowed time. If so,
|
121
|
+
# the client object is removed from the timeout array, a 408 response is written.
|
122
|
+
# Then its connection is closed, and the object is removed from the `sockets` array
|
114
123
|
# that watches for new data.
|
115
124
|
#
|
116
125
|
# This behavior loops until all the objects that have timed out have been removed.
|
117
126
|
#
|
118
|
-
# Once all the timeouts have been processed, the next duration of the `
|
127
|
+
# Once all the timeouts have been processed, the next duration of the `NIO::Selector#select` sleep
|
119
128
|
# will be set to be equal to the amount of time it will take for the next timeout to occur.
|
120
129
|
# This calculation happens in `calculate_sleep`.
|
121
130
|
def run_internal
|
122
|
-
|
131
|
+
monitors = @monitors
|
132
|
+
selector = @selector
|
123
133
|
|
124
134
|
while true
|
125
135
|
begin
|
126
|
-
ready =
|
136
|
+
ready = selector.select @sleep_for
|
127
137
|
rescue IOError => e
|
128
138
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
129
|
-
if
|
139
|
+
if monitors.any? { |mon| mon.value.closed? }
|
130
140
|
STDERR.puts "Error in select: #{e.message} (#{e.class})"
|
131
141
|
STDERR.puts e.backtrace
|
132
|
-
|
142
|
+
|
143
|
+
monitors.reject! do |mon|
|
144
|
+
if mon.value.closed?
|
145
|
+
selector.deregister mon.value
|
146
|
+
true
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
133
150
|
retry
|
134
151
|
else
|
135
152
|
raise
|
136
153
|
end
|
137
154
|
end
|
138
155
|
|
139
|
-
if ready
|
140
|
-
|
141
|
-
if
|
156
|
+
if ready
|
157
|
+
ready.each do |mon|
|
158
|
+
if mon.value == @ready
|
142
159
|
@mutex.synchronize do
|
143
160
|
case @ready.read(1)
|
144
161
|
when "*"
|
145
|
-
|
162
|
+
@input.each do |c|
|
163
|
+
mon = nil
|
164
|
+
begin
|
165
|
+
begin
|
166
|
+
mon = selector.register(c, :r)
|
167
|
+
rescue ArgumentError
|
168
|
+
# There is a bug where we seem to be registering an already registered
|
169
|
+
# client. This code deals with this situation but I wish we didn't have to.
|
170
|
+
monitors.delete_if { |submon| submon.value.to_io == c.to_io }
|
171
|
+
selector.deregister(c)
|
172
|
+
mon = selector.register(c, :r)
|
173
|
+
end
|
174
|
+
rescue IOError
|
175
|
+
# Means that the io is closed, so we should ignore this request
|
176
|
+
# entirely
|
177
|
+
else
|
178
|
+
mon.value = c
|
179
|
+
@timeouts << mon if c.timeout_at
|
180
|
+
monitors << mon
|
181
|
+
end
|
182
|
+
end
|
146
183
|
@input.clear
|
184
|
+
|
185
|
+
@timeouts.sort! { |a,b| a.value.timeout_at <=> b.value.timeout_at }
|
186
|
+
calculate_sleep
|
147
187
|
when "c"
|
148
|
-
|
149
|
-
if
|
188
|
+
monitors.reject! do |submon|
|
189
|
+
if submon.value == @ready
|
150
190
|
false
|
151
191
|
else
|
152
|
-
|
192
|
+
submon.value.close
|
193
|
+
begin
|
194
|
+
selector.deregister submon.value
|
195
|
+
rescue IOError
|
196
|
+
# nio4r on jruby seems to throw an IOError here if the IO is closed, so
|
197
|
+
# we need to swallow it.
|
198
|
+
end
|
153
199
|
true
|
154
200
|
end
|
155
201
|
end
|
@@ -158,19 +204,21 @@ module Puma
|
|
158
204
|
end
|
159
205
|
end
|
160
206
|
else
|
207
|
+
c = mon.value
|
208
|
+
|
161
209
|
# We have to be sure to remove it from the timeout
|
162
210
|
# list or we'll accidentally close the socket when
|
163
211
|
# it's in use!
|
164
212
|
if c.timeout_at
|
165
213
|
@mutex.synchronize do
|
166
|
-
@timeouts.delete
|
214
|
+
@timeouts.delete mon
|
167
215
|
end
|
168
216
|
end
|
169
217
|
|
170
218
|
begin
|
171
219
|
if c.try_to_finish
|
172
220
|
@app_pool << c
|
173
|
-
|
221
|
+
clear_monitor mon
|
174
222
|
end
|
175
223
|
|
176
224
|
# Don't report these to the lowlevel_error handler, otherwise
|
@@ -180,18 +228,23 @@ module Puma
|
|
180
228
|
c.write_500
|
181
229
|
c.close
|
182
230
|
|
183
|
-
|
231
|
+
clear_monitor mon
|
184
232
|
|
185
233
|
# SSL handshake failure
|
186
234
|
rescue MiniSSL::SSLError => e
|
187
235
|
@server.lowlevel_error(e, c.env)
|
188
236
|
|
189
237
|
ssl_socket = c.io
|
190
|
-
|
238
|
+
begin
|
239
|
+
addr = ssl_socket.peeraddr.last
|
240
|
+
rescue IOError
|
241
|
+
addr = "<unknown>"
|
242
|
+
end
|
243
|
+
|
191
244
|
cert = ssl_socket.peercert
|
192
245
|
|
193
246
|
c.close
|
194
|
-
|
247
|
+
clear_monitor mon
|
195
248
|
|
196
249
|
@events.ssl_error @server, addr, cert, e
|
197
250
|
|
@@ -202,7 +255,7 @@ module Puma
|
|
202
255
|
c.write_400
|
203
256
|
c.close
|
204
257
|
|
205
|
-
|
258
|
+
clear_monitor mon
|
206
259
|
|
207
260
|
@events.parse_error @server, c.env, e
|
208
261
|
rescue StandardError => e
|
@@ -211,7 +264,7 @@ module Puma
|
|
211
264
|
c.write_500
|
212
265
|
c.close
|
213
266
|
|
214
|
-
|
267
|
+
clear_monitor mon
|
215
268
|
end
|
216
269
|
end
|
217
270
|
end
|
@@ -221,11 +274,13 @@ module Puma
|
|
221
274
|
@mutex.synchronize do
|
222
275
|
now = Time.now
|
223
276
|
|
224
|
-
while @timeouts.first.timeout_at < now
|
225
|
-
|
277
|
+
while @timeouts.first.value.timeout_at < now
|
278
|
+
mon = @timeouts.shift
|
279
|
+
c = mon.value
|
226
280
|
c.write_408 if c.in_data_phase
|
227
281
|
c.close
|
228
|
-
|
282
|
+
|
283
|
+
clear_monitor mon
|
229
284
|
|
230
285
|
break if @timeouts.empty?
|
231
286
|
end
|
@@ -236,6 +291,11 @@ module Puma
|
|
236
291
|
end
|
237
292
|
end
|
238
293
|
|
294
|
+
def clear_monitor(mon)
|
295
|
+
@selector.deregister mon.value
|
296
|
+
@monitors.delete mon
|
297
|
+
end
|
298
|
+
|
239
299
|
public
|
240
300
|
|
241
301
|
def run
|
@@ -260,7 +320,7 @@ module Puma
|
|
260
320
|
end
|
261
321
|
end
|
262
322
|
|
263
|
-
# The `calculate_sleep` sets the value that the `
|
323
|
+
# The `calculate_sleep` sets the value that the `NIO::Selector#select` will
|
264
324
|
# sleep for in the main reactor loop when no sockets are being written to.
|
265
325
|
#
|
266
326
|
# The values kept in `@timeouts` are sorted so that the first timeout
|
@@ -274,7 +334,7 @@ module Puma
|
|
274
334
|
if @timeouts.empty?
|
275
335
|
@sleep_for = DefaultSleepFor
|
276
336
|
else
|
277
|
-
diff = @timeouts.first.timeout_at.to_f - Time.now.to_f
|
337
|
+
diff = @timeouts.first.value.timeout_at.to_f - Time.now.to_f
|
278
338
|
|
279
339
|
if diff < 0.0
|
280
340
|
@sleep_for = 0
|
@@ -291,18 +351,18 @@ module Puma
|
|
291
351
|
# object.
|
292
352
|
#
|
293
353
|
# The main body of the reactor loop is in `run_internal` and it
|
294
|
-
# will sleep on `
|
295
|
-
# reactor it cannot be added directly to the `sockets`
|
296
|
-
# the `
|
354
|
+
# will sleep on `NIO::Selector#select`. When a new connection is added to the
|
355
|
+
# reactor it cannot be added directly to the `sockets` array, because
|
356
|
+
# the `NIO::Selector#select` will not be watching for it yet.
|
297
357
|
#
|
298
|
-
# Instead what needs to happen is that `
|
358
|
+
# Instead what needs to happen is that `NIO::Selector#select` needs to be woken up,
|
299
359
|
# the contents of `@input` added to the `sockets` array, and then
|
300
|
-
# another call to `
|
360
|
+
# another call to `NIO::Selector#select` needs to happen. Since the `Puma::Client`
|
301
361
|
# object can be read immediately, it does not block, but instead returns
|
302
362
|
# right away.
|
303
363
|
#
|
304
364
|
# This behavior is accomplished by writing to `@trigger` which wakes up
|
305
|
-
# the `
|
365
|
+
# the `NIO::Selector#select` and then there is logic to detect the value of `*`,
|
306
366
|
# pull the contents from `@input` and add them to the sockets array.
|
307
367
|
#
|
308
368
|
# If the object passed in has a timeout value in `timeout_at` then
|
@@ -313,13 +373,6 @@ module Puma
|
|
313
373
|
@mutex.synchronize do
|
314
374
|
@input << c
|
315
375
|
@trigger << "*"
|
316
|
-
|
317
|
-
if c.timeout_at
|
318
|
-
@timeouts << c
|
319
|
-
@timeouts.sort! { |a,b| a.timeout_at <=> b.timeout_at }
|
320
|
-
|
321
|
-
calculate_sleep
|
322
|
-
end
|
323
376
|
end
|
324
377
|
end
|
325
378
|
|