puma 3.12.2 → 4.2.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.
- checksums.yaml +4 -4
- data/History.md +106 -6
- data/README.md +91 -43
- data/docs/architecture.md +1 -0
- data/docs/deployment.md +24 -4
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/plugins.md +20 -10
- data/docs/restart.md +4 -2
- data/docs/systemd.md +27 -9
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/extconf.rb +8 -0
- data/ext/puma_http11/http11_parser.c +37 -62
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +78 -8
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -4
- data/lib/puma.rb +8 -0
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +35 -29
- data/lib/puma/binder.rb +39 -5
- data/lib/puma/cli.rb +4 -0
- data/lib/puma/client.rb +221 -199
- data/lib/puma/cluster.rb +53 -30
- data/lib/puma/configuration.rb +4 -3
- data/lib/puma/const.rb +22 -25
- data/lib/puma/control_cli.rb +21 -4
- data/lib/puma/dsl.rb +297 -75
- data/lib/puma/events.rb +4 -1
- data/lib/puma/io_buffer.rb +1 -6
- data/lib/puma/launcher.rb +95 -53
- data/lib/puma/minissl.rb +35 -17
- data/lib/puma/plugin.rb +5 -2
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/rack/builder.rb +2 -0
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +109 -57
- data/lib/puma/runner.rb +4 -3
- data/lib/puma/server.rb +59 -62
- data/lib/puma/single.rb +3 -3
- data/lib/puma/thread_pool.rb +14 -32
- data/lib/puma/util.rb +1 -6
- data/lib/rack/handler/puma.rb +3 -3
- data/tools/docker/Dockerfile +16 -0
- data/tools/jungle/init.d/puma +6 -6
- data/tools/trickletest.rb +0 -1
- metadata +20 -8
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
- data/lib/puma/java_io_buffer.rb +0 -47
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
data/lib/puma/events.rb
CHANGED
@@ -93,7 +93,10 @@ module Puma
|
|
93
93
|
# parsing exception.
|
94
94
|
#
|
95
95
|
def parse_error(server, env, error)
|
96
|
-
@stderr.puts "#{Time.now}: HTTP parse error, malformed request
|
96
|
+
@stderr.puts "#{Time.now}: HTTP parse error, malformed request " \
|
97
|
+
"(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \
|
98
|
+
"#{error.inspect}" \
|
99
|
+
"\n---\n"
|
97
100
|
end
|
98
101
|
|
99
102
|
# An SSL error has occurred.
|
data/lib/puma/io_buffer.rb
CHANGED
data/lib/puma/launcher.rb
CHANGED
@@ -2,12 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'puma/events'
|
4
4
|
require 'puma/detect'
|
5
|
-
|
6
5
|
require 'puma/cluster'
|
7
6
|
require 'puma/single'
|
8
|
-
|
9
7
|
require 'puma/const'
|
10
|
-
|
11
8
|
require 'puma/binder'
|
12
9
|
|
13
10
|
module Puma
|
@@ -63,6 +60,9 @@ module Puma
|
|
63
60
|
@options = @config.options
|
64
61
|
@config.clamp
|
65
62
|
|
63
|
+
@events.formatter = Events::PidFormatter.new if clustered?
|
64
|
+
@events.formatter = options[:log_formatter] if @options[:log_formatter]
|
65
|
+
|
66
66
|
generate_restart_data
|
67
67
|
|
68
68
|
if clustered? && !Process.respond_to?(:fork)
|
@@ -81,7 +81,6 @@ module Puma
|
|
81
81
|
set_rack_environment
|
82
82
|
|
83
83
|
if clustered?
|
84
|
-
@events.formatter = Events::PidFormatter.new
|
85
84
|
@options[:logger] = @events
|
86
85
|
|
87
86
|
@runner = Cluster.new(self, @events)
|
@@ -124,19 +123,6 @@ module Puma
|
|
124
123
|
File.unlink(path) if path && File.exist?(path)
|
125
124
|
end
|
126
125
|
|
127
|
-
# If configured, write the pid of the current process out
|
128
|
-
# to a file.
|
129
|
-
def write_pid
|
130
|
-
path = @options[:pidfile]
|
131
|
-
return unless path
|
132
|
-
|
133
|
-
File.open(path, 'w') { |f| f.puts Process.pid }
|
134
|
-
cur = Process.pid
|
135
|
-
at_exit do
|
136
|
-
delete_pidfile if cur == Process.pid
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
126
|
# Begin async shutdown of the server
|
141
127
|
def halt
|
142
128
|
@status = :halt
|
@@ -198,6 +184,7 @@ module Puma
|
|
198
184
|
when :exit
|
199
185
|
# nothing
|
200
186
|
end
|
187
|
+
@binder.close_unix_paths
|
201
188
|
end
|
202
189
|
|
203
190
|
# Return which tcp port the launcher is using, if it's using TCP
|
@@ -214,8 +201,25 @@ module Puma
|
|
214
201
|
end
|
215
202
|
end
|
216
203
|
|
204
|
+
def close_binder_listeners
|
205
|
+
@binder.close_listeners
|
206
|
+
end
|
207
|
+
|
217
208
|
private
|
218
209
|
|
210
|
+
# If configured, write the pid of the current process out
|
211
|
+
# to a file.
|
212
|
+
def write_pid
|
213
|
+
path = @options[:pidfile]
|
214
|
+
return unless path
|
215
|
+
|
216
|
+
File.open(path, 'w') { |f| f.puts Process.pid }
|
217
|
+
cur = Process.pid
|
218
|
+
at_exit do
|
219
|
+
delete_pidfile if cur == Process.pid
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
219
223
|
def reload_worker_directory
|
220
224
|
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
221
225
|
end
|
@@ -235,48 +239,71 @@ module Puma
|
|
235
239
|
Dir.chdir(@restart_dir)
|
236
240
|
Kernel.exec(*argv)
|
237
241
|
else
|
238
|
-
redirects = {:close_others => true}
|
239
|
-
@binder.listeners.each_with_index do |(l, io), i|
|
240
|
-
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
241
|
-
redirects[io.to_i] = io.to_i
|
242
|
-
end
|
243
|
-
|
244
242
|
argv = restart_args
|
245
243
|
Dir.chdir(@restart_dir)
|
246
|
-
argv += [
|
244
|
+
argv += [@binder.redirects_for_restart]
|
247
245
|
Kernel.exec(*argv)
|
248
246
|
end
|
249
247
|
end
|
250
248
|
|
251
|
-
def
|
252
|
-
|
253
|
-
|
254
|
-
|
249
|
+
def dependencies_and_files_to_require_after_prune
|
250
|
+
puma = spec_for_gem("puma")
|
251
|
+
|
252
|
+
deps = puma.runtime_dependencies.map do |d|
|
253
|
+
"#{d.name}:#{spec_for_gem(d.name).version}"
|
254
|
+
end
|
255
|
+
|
256
|
+
[deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
|
257
|
+
end
|
258
|
+
|
259
|
+
def extra_runtime_deps_directories
|
260
|
+
Array(@options[:extra_runtime_dependencies]).map do |d_name|
|
261
|
+
if (spec = spec_for_gem(d_name))
|
262
|
+
require_paths_for_gem(spec)
|
263
|
+
else
|
264
|
+
log "* Could not load extra dependency: #{d_name}"
|
265
|
+
nil
|
266
|
+
end
|
267
|
+
end.flatten.compact
|
268
|
+
end
|
269
|
+
|
270
|
+
def puma_wild_location
|
271
|
+
puma = spec_for_gem("puma")
|
272
|
+
dirs = require_paths_for_gem(puma)
|
255
273
|
puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
|
274
|
+
File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
|
275
|
+
end
|
256
276
|
|
257
|
-
|
277
|
+
def prune_bundler
|
278
|
+
return unless defined?(Bundler)
|
279
|
+
require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
|
280
|
+
unless puma_wild_location
|
258
281
|
log "! Unable to prune Bundler environment, continuing"
|
259
282
|
return
|
260
283
|
end
|
261
284
|
|
262
|
-
deps =
|
263
|
-
spec = Bundler.rubygems.loaded_specs(d.name)
|
264
|
-
"#{d.name}:#{spec.version.to_s}"
|
265
|
-
end
|
285
|
+
deps, dirs = dependencies_and_files_to_require_after_prune
|
266
286
|
|
267
287
|
log '* Pruning Bundler environment'
|
268
288
|
home = ENV['GEM_HOME']
|
269
289
|
Bundler.with_clean_env do
|
270
290
|
ENV['GEM_HOME'] = home
|
271
291
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
272
|
-
|
273
|
-
args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
292
|
+
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
274
293
|
# Ruby 2.0+ defaults to true which breaks socket activation
|
275
|
-
args += [{:close_others => false}]
|
294
|
+
args += [{:close_others => false}]
|
276
295
|
Kernel.exec(*args)
|
277
296
|
end
|
278
297
|
end
|
279
298
|
|
299
|
+
def spec_for_gem(gem_name)
|
300
|
+
Bundler.rubygems.loaded_specs(gem_name)
|
301
|
+
end
|
302
|
+
|
303
|
+
def require_paths_for_gem(gem_spec)
|
304
|
+
gem_spec.full_require_paths
|
305
|
+
end
|
306
|
+
|
280
307
|
def log(str)
|
281
308
|
@events.log str
|
282
309
|
end
|
@@ -296,6 +323,21 @@ module Puma
|
|
296
323
|
log "- Goodbye!"
|
297
324
|
end
|
298
325
|
|
326
|
+
def log_thread_status
|
327
|
+
Thread.list.each do |thread|
|
328
|
+
log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
|
329
|
+
logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
|
330
|
+
logstr += " #{thread.name}" if thread.respond_to?(:name)
|
331
|
+
log logstr
|
332
|
+
|
333
|
+
if thread.backtrace
|
334
|
+
log thread.backtrace.join("\n")
|
335
|
+
else
|
336
|
+
log "<no backtrace available>"
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
299
341
|
def set_process_title
|
300
342
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
301
343
|
end
|
@@ -319,16 +361,6 @@ module Puma
|
|
319
361
|
@options[:prune_bundler] && clustered? && !@options[:preload_app]
|
320
362
|
end
|
321
363
|
|
322
|
-
def close_binder_listeners
|
323
|
-
@binder.listeners.each do |l, io|
|
324
|
-
io.close
|
325
|
-
uri = URI.parse(l)
|
326
|
-
next unless uri.scheme == 'unix'
|
327
|
-
File.unlink("#{uri.host}#{uri.path}")
|
328
|
-
end
|
329
|
-
end
|
330
|
-
|
331
|
-
|
332
364
|
def generate_restart_data
|
333
365
|
if dir = @options[:directory]
|
334
366
|
@restart_dir = dir
|
@@ -397,7 +429,7 @@ module Puma
|
|
397
429
|
Signal.trap "SIGTERM" do
|
398
430
|
graceful_stop
|
399
431
|
|
400
|
-
raise
|
432
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
401
433
|
end
|
402
434
|
rescue Exception
|
403
435
|
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
|
@@ -405,12 +437,6 @@ module Puma
|
|
405
437
|
|
406
438
|
begin
|
407
439
|
Signal.trap "SIGINT" do
|
408
|
-
if Puma.jruby?
|
409
|
-
@status = :exit
|
410
|
-
graceful_stop
|
411
|
-
exit
|
412
|
-
end
|
413
|
-
|
414
440
|
stop
|
415
441
|
end
|
416
442
|
rescue Exception
|
@@ -428,6 +454,22 @@ module Puma
|
|
428
454
|
rescue Exception
|
429
455
|
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
|
430
456
|
end
|
457
|
+
|
458
|
+
begin
|
459
|
+
Signal.trap "SIGINFO" do
|
460
|
+
log_thread_status
|
461
|
+
end
|
462
|
+
rescue Exception
|
463
|
+
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
|
464
|
+
# to see this constantly on Linux.
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def require_rubygems_min_version!(min_version, feature)
|
469
|
+
return if min_version <= Gem::Version.new(Gem::VERSION)
|
470
|
+
|
471
|
+
raise "#{feature} is not supported on your version of RubyGems. " \
|
472
|
+
"You must have RubyGems #{min_version}+ to use this feature."
|
431
473
|
end
|
432
474
|
end
|
433
475
|
end
|
data/lib/puma/minissl.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
begin
|
4
4
|
require 'io/wait'
|
5
|
-
|
5
|
+
rescue LoadError
|
6
6
|
end
|
7
7
|
|
8
8
|
module Puma
|
@@ -54,22 +54,21 @@ module Puma
|
|
54
54
|
output = engine_read_all
|
55
55
|
return output if output
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end while true
|
57
|
+
data = @socket.read_nonblock(size, exception: false)
|
58
|
+
if data == :wait_readable || data == :wait_writable
|
59
|
+
# It would make more sense to let @socket.read_nonblock raise
|
60
|
+
# EAGAIN if necessary but it seems like it'll misbehave on Windows.
|
61
|
+
# I don't have a Windows machine to debug this so I can't explain
|
62
|
+
# exactly whats happening in that OS. Please let me know if you
|
63
|
+
# find out!
|
64
|
+
#
|
65
|
+
# In the meantime, we can emulate the correct behavior by
|
66
|
+
# capturing :wait_readable & :wait_writable and raising EAGAIN
|
67
|
+
# ourselves.
|
68
|
+
raise IO::EAGAINWaitReadable
|
69
|
+
elsif data.nil?
|
70
|
+
return nil
|
71
|
+
end
|
73
72
|
|
74
73
|
@engine.inject(data)
|
75
74
|
output = engine_read_all
|
@@ -177,6 +176,12 @@ module Puma
|
|
177
176
|
|
178
177
|
class Context
|
179
178
|
attr_accessor :verify_mode
|
179
|
+
attr_reader :no_tlsv1, :no_tlsv1_1
|
180
|
+
|
181
|
+
def initialize
|
182
|
+
@no_tlsv1 = false
|
183
|
+
@no_tlsv1_1 = false
|
184
|
+
end
|
180
185
|
|
181
186
|
if defined?(JRUBY_VERSION)
|
182
187
|
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
@@ -220,6 +225,19 @@ module Puma
|
|
220
225
|
raise "Cert not configured" unless @cert
|
221
226
|
end
|
222
227
|
end
|
228
|
+
|
229
|
+
# disables TLSv1
|
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
|
+
|
235
|
+
# disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
|
236
|
+
def no_tlsv1_1=(tlsv1_1)
|
237
|
+
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
|
238
|
+
@no_tlsv1_1 = tlsv1_1
|
239
|
+
end
|
240
|
+
|
223
241
|
end
|
224
242
|
|
225
243
|
VERIFY_NONE = 0
|
data/lib/puma/plugin.rb
CHANGED
data/lib/puma/rack/builder.rb
CHANGED
data/lib/puma/rack/urlmap.rb
CHANGED
data/lib/puma/rack_default.rb
CHANGED
data/lib/puma/reactor.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
require 'puma/util'
|
4
4
|
require 'puma/minissl'
|
5
5
|
|
6
|
+
require 'nio'
|
7
|
+
|
6
8
|
module Puma
|
7
9
|
# Internal Docs, Not a public interface.
|
8
10
|
#
|
@@ -18,12 +20,13 @@ module Puma
|
|
18
20
|
#
|
19
21
|
# ## Reactor Flow
|
20
22
|
#
|
21
|
-
# A
|
22
|
-
#
|
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.
|
23
25
|
#
|
24
|
-
#
|
26
|
+
# The waiting/wake up is performed with nio4r, which will use the appropriate backend (libev, Java NIO or
|
27
|
+
# just plain IO#select). The call to `NIO::Selector#select` will "wake up" and
|
25
28
|
# return the references to any objects that caused it to "wake". The reactor
|
26
|
-
# 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
|
27
30
|
# have a full header and body then the reactor passes the request to a thread pool.
|
28
31
|
# Once in a thread pool, a "worker thread" can run the the application's Ruby code against the request.
|
29
32
|
#
|
@@ -38,7 +41,7 @@ module Puma
|
|
38
41
|
# Creates an instance of Puma::Reactor
|
39
42
|
#
|
40
43
|
# The `server` argument is an instance of `Puma::Server`
|
41
|
-
#
|
44
|
+
# that is used to write a response for "low level errors"
|
42
45
|
# when there is an exception inside of the reactor.
|
43
46
|
#
|
44
47
|
# The `app_pool` is an instance of `Puma::ThreadPool`.
|
@@ -49,6 +52,8 @@ module Puma
|
|
49
52
|
@events = server.events
|
50
53
|
@app_pool = app_pool
|
51
54
|
|
55
|
+
@selector = NIO::Selector.new
|
56
|
+
|
52
57
|
@mutex = Mutex.new
|
53
58
|
|
54
59
|
# Read / Write pipes to wake up internal while loop
|
@@ -57,24 +62,26 @@ module Puma
|
|
57
62
|
@sleep_for = DefaultSleepFor
|
58
63
|
@timeouts = []
|
59
64
|
|
60
|
-
|
65
|
+
mon = @selector.register(@ready, :r)
|
66
|
+
mon.value = @ready
|
67
|
+
|
68
|
+
@monitors = [mon]
|
61
69
|
end
|
62
70
|
|
63
71
|
private
|
64
72
|
|
65
|
-
|
66
73
|
# Until a request is added via the `add` method this method will internally
|
67
74
|
# loop, waiting on the `sockets` array objects. The only object in this
|
68
75
|
# array at first is the `@ready` IO object, which is the read end of a pipe
|
69
76
|
# connected to `@trigger` object. When `@trigger` is written to, then the loop
|
70
|
-
# will break on `
|
77
|
+
# will break on `NIO::Selector#select` and return an array.
|
71
78
|
#
|
72
79
|
# ## When a request is added:
|
73
80
|
#
|
74
81
|
# When the `add` method is called, an instance of `Puma::Client` is added to the `@input` array.
|
75
82
|
# Next the `@ready` pipe is "woken" by writing a string of `"*"` to `@trigger`.
|
76
83
|
#
|
77
|
-
# 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
|
78
85
|
# to whatever "woke" it up. On the very first loop, the only thing in `sockets` is `@ready`.
|
79
86
|
# When `@trigger` is written-to, the loop "wakes" and the `ready`
|
80
87
|
# variable returns an array of arrays that looks like `[[#<IO:fd 10>], [], []]` where the
|
@@ -90,11 +97,11 @@ module Puma
|
|
90
97
|
# to the `@ready` IO object. For example: `[#<IO:fd 10>, #<Puma::Client:0x3fdc1103bee8 @ready=false>]`.
|
91
98
|
#
|
92
99
|
# Since the `Puma::Client` in this example has data that has not been read yet,
|
93
|
-
# the `
|
100
|
+
# the `NIO::Selector#select` is immediately able to "wake" and read from the `Puma::Client`. At this point the
|
94
101
|
# `ready` output looks like this: `[[#<Puma::Client:0x3fdc1103bee8 @ready=false>], [], []]`.
|
95
102
|
#
|
96
103
|
# Each element in the first entry is iterated over. The `Puma::Client` object is not
|
97
|
-
# 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
|
98
105
|
# the `Puma::Client#try_to_finish` method. If the full request has been sent,
|
99
106
|
# then the request is passed off to the `@app_pool` thread pool so that a "worker thread"
|
100
107
|
# can pick up the request and begin to execute application logic. This is done
|
@@ -102,56 +109,93 @@ module Puma
|
|
102
109
|
#
|
103
110
|
# If the request body is not present then nothing will happen, and the loop will iterate
|
104
111
|
# again. When the client sends more data to the socket the `Puma::Client` object will
|
105
|
-
# wake up the `
|
112
|
+
# wake up the `NIO::Selector#select` and it can again be checked to see if it's ready to be
|
106
113
|
# passed to the thread pool.
|
107
114
|
#
|
108
115
|
# ## Time Out Case
|
109
116
|
#
|
110
|
-
# 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
|
111
118
|
# periodically "time out" of the sleep. One of the functions of this is to check for
|
112
119
|
# any requests that have "timed out". At the end of the loop it's checked to see if
|
113
|
-
# the first element in the `@timeout` array has exceed
|
114
|
-
# the client object is removed from the timeout
|
115
|
-
# 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
|
116
123
|
# that watches for new data.
|
117
124
|
#
|
118
125
|
# This behavior loops until all the objects that have timed out have been removed.
|
119
126
|
#
|
120
|
-
# 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
|
121
128
|
# will be set to be equal to the amount of time it will take for the next timeout to occur.
|
122
129
|
# This calculation happens in `calculate_sleep`.
|
123
130
|
def run_internal
|
124
|
-
|
131
|
+
monitors = @monitors
|
132
|
+
selector = @selector
|
125
133
|
|
126
134
|
while true
|
127
135
|
begin
|
128
|
-
ready =
|
136
|
+
ready = selector.select @sleep_for
|
129
137
|
rescue IOError => e
|
130
138
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
131
|
-
if
|
139
|
+
if monitors.any? { |mon| mon.value.closed? }
|
132
140
|
STDERR.puts "Error in select: #{e.message} (#{e.class})"
|
133
141
|
STDERR.puts e.backtrace
|
134
|
-
|
142
|
+
|
143
|
+
monitors.reject! do |mon|
|
144
|
+
if mon.value.closed?
|
145
|
+
selector.deregister mon.value
|
146
|
+
true
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
135
150
|
retry
|
136
151
|
else
|
137
152
|
raise
|
138
153
|
end
|
139
154
|
end
|
140
155
|
|
141
|
-
if ready
|
142
|
-
|
143
|
-
if
|
156
|
+
if ready
|
157
|
+
ready.each do |mon|
|
158
|
+
if mon.value == @ready
|
144
159
|
@mutex.synchronize do
|
145
160
|
case @ready.read(1)
|
146
161
|
when "*"
|
147
|
-
|
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
|
148
183
|
@input.clear
|
184
|
+
|
185
|
+
@timeouts.sort! { |a,b| a.value.timeout_at <=> b.value.timeout_at }
|
186
|
+
calculate_sleep
|
149
187
|
when "c"
|
150
|
-
|
151
|
-
if
|
188
|
+
monitors.reject! do |submon|
|
189
|
+
if submon.value == @ready
|
152
190
|
false
|
153
191
|
else
|
154
|
-
|
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
|
155
199
|
true
|
156
200
|
end
|
157
201
|
end
|
@@ -160,40 +204,47 @@ module Puma
|
|
160
204
|
end
|
161
205
|
end
|
162
206
|
else
|
207
|
+
c = mon.value
|
208
|
+
|
163
209
|
# We have to be sure to remove it from the timeout
|
164
210
|
# list or we'll accidentally close the socket when
|
165
211
|
# it's in use!
|
166
212
|
if c.timeout_at
|
167
213
|
@mutex.synchronize do
|
168
|
-
@timeouts.delete
|
214
|
+
@timeouts.delete mon
|
169
215
|
end
|
170
216
|
end
|
171
217
|
|
172
218
|
begin
|
173
219
|
if c.try_to_finish
|
174
220
|
@app_pool << c
|
175
|
-
|
221
|
+
clear_monitor mon
|
176
222
|
end
|
177
223
|
|
178
224
|
# Don't report these to the lowlevel_error handler, otherwise
|
179
225
|
# will be flooding them with errors when persistent connections
|
180
226
|
# are closed.
|
181
227
|
rescue ConnectionError
|
182
|
-
c.
|
228
|
+
c.write_error(500)
|
183
229
|
c.close
|
184
230
|
|
185
|
-
|
231
|
+
clear_monitor mon
|
186
232
|
|
187
233
|
# SSL handshake failure
|
188
234
|
rescue MiniSSL::SSLError => e
|
189
235
|
@server.lowlevel_error(e, c.env)
|
190
236
|
|
191
237
|
ssl_socket = c.io
|
192
|
-
|
238
|
+
begin
|
239
|
+
addr = ssl_socket.peeraddr.last
|
240
|
+
rescue IOError
|
241
|
+
addr = "<unknown>"
|
242
|
+
end
|
243
|
+
|
193
244
|
cert = ssl_socket.peercert
|
194
245
|
|
195
246
|
c.close
|
196
|
-
|
247
|
+
clear_monitor mon
|
197
248
|
|
198
249
|
@events.ssl_error @server, addr, cert, e
|
199
250
|
|
@@ -201,19 +252,19 @@ module Puma
|
|
201
252
|
rescue HttpParserError => e
|
202
253
|
@server.lowlevel_error(e, c.env)
|
203
254
|
|
204
|
-
c.
|
255
|
+
c.write_error(400)
|
205
256
|
c.close
|
206
257
|
|
207
|
-
|
258
|
+
clear_monitor mon
|
208
259
|
|
209
260
|
@events.parse_error @server, c.env, e
|
210
261
|
rescue StandardError => e
|
211
262
|
@server.lowlevel_error(e, c.env)
|
212
263
|
|
213
|
-
c.
|
264
|
+
c.write_error(500)
|
214
265
|
c.close
|
215
266
|
|
216
|
-
|
267
|
+
clear_monitor mon
|
217
268
|
end
|
218
269
|
end
|
219
270
|
end
|
@@ -223,11 +274,13 @@ module Puma
|
|
223
274
|
@mutex.synchronize do
|
224
275
|
now = Time.now
|
225
276
|
|
226
|
-
while @timeouts.first.timeout_at < now
|
227
|
-
|
228
|
-
c
|
277
|
+
while @timeouts.first.value.timeout_at < now
|
278
|
+
mon = @timeouts.shift
|
279
|
+
c = mon.value
|
280
|
+
c.write_error(408) if c.in_data_phase
|
229
281
|
c.close
|
230
|
-
|
282
|
+
|
283
|
+
clear_monitor mon
|
231
284
|
|
232
285
|
break if @timeouts.empty?
|
233
286
|
end
|
@@ -238,6 +291,11 @@ module Puma
|
|
238
291
|
end
|
239
292
|
end
|
240
293
|
|
294
|
+
def clear_monitor(mon)
|
295
|
+
@selector.deregister mon.value
|
296
|
+
@monitors.delete mon
|
297
|
+
end
|
298
|
+
|
241
299
|
public
|
242
300
|
|
243
301
|
def run
|
@@ -249,6 +307,7 @@ module Puma
|
|
249
307
|
|
250
308
|
def run_in_thread
|
251
309
|
@thread = Thread.new do
|
310
|
+
Puma.set_thread_name "reactor"
|
252
311
|
begin
|
253
312
|
run_internal
|
254
313
|
rescue StandardError => e
|
@@ -262,7 +321,7 @@ module Puma
|
|
262
321
|
end
|
263
322
|
end
|
264
323
|
|
265
|
-
# The `calculate_sleep` sets the value that the `
|
324
|
+
# The `calculate_sleep` sets the value that the `NIO::Selector#select` will
|
266
325
|
# sleep for in the main reactor loop when no sockets are being written to.
|
267
326
|
#
|
268
327
|
# The values kept in `@timeouts` are sorted so that the first timeout
|
@@ -276,7 +335,7 @@ module Puma
|
|
276
335
|
if @timeouts.empty?
|
277
336
|
@sleep_for = DefaultSleepFor
|
278
337
|
else
|
279
|
-
diff = @timeouts.first.timeout_at.to_f - Time.now.to_f
|
338
|
+
diff = @timeouts.first.value.timeout_at.to_f - Time.now.to_f
|
280
339
|
|
281
340
|
if diff < 0.0
|
282
341
|
@sleep_for = 0
|
@@ -293,18 +352,18 @@ module Puma
|
|
293
352
|
# object.
|
294
353
|
#
|
295
354
|
# The main body of the reactor loop is in `run_internal` and it
|
296
|
-
# will sleep on `
|
297
|
-
# reactor it cannot be added directly to the `sockets`
|
298
|
-
# the `
|
355
|
+
# will sleep on `NIO::Selector#select`. When a new connection is added to the
|
356
|
+
# reactor it cannot be added directly to the `sockets` array, because
|
357
|
+
# the `NIO::Selector#select` will not be watching for it yet.
|
299
358
|
#
|
300
|
-
# Instead what needs to happen is that `
|
359
|
+
# Instead what needs to happen is that `NIO::Selector#select` needs to be woken up,
|
301
360
|
# the contents of `@input` added to the `sockets` array, and then
|
302
|
-
# another call to `
|
361
|
+
# another call to `NIO::Selector#select` needs to happen. Since the `Puma::Client`
|
303
362
|
# object can be read immediately, it does not block, but instead returns
|
304
363
|
# right away.
|
305
364
|
#
|
306
365
|
# This behavior is accomplished by writing to `@trigger` which wakes up
|
307
|
-
# the `
|
366
|
+
# the `NIO::Selector#select` and then there is logic to detect the value of `*`,
|
308
367
|
# pull the contents from `@input` and add them to the sockets array.
|
309
368
|
#
|
310
369
|
# If the object passed in has a timeout value in `timeout_at` then
|
@@ -315,13 +374,6 @@ module Puma
|
|
315
374
|
@mutex.synchronize do
|
316
375
|
@input << c
|
317
376
|
@trigger << "*"
|
318
|
-
|
319
|
-
if c.timeout_at
|
320
|
-
@timeouts << c
|
321
|
-
@timeouts.sort! { |a,b| a.timeout_at <=> b.timeout_at }
|
322
|
-
|
323
|
-
calculate_sleep
|
324
|
-
end
|
325
377
|
end
|
326
378
|
end
|
327
379
|
|