puma 6.3.0 → 6.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +108 -8
- data/README.md +19 -9
- data/docs/kubernetes.md +12 -0
- data/docs/restart.md +1 -0
- data/docs/systemd.md +2 -4
- data/ext/puma_http11/extconf.rb +5 -1
- data/ext/puma_http11/mini_ssl.c +66 -7
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +2 -1
- data/lib/puma/binder.rb +2 -2
- data/lib/puma/cli.rb +4 -0
- data/lib/puma/client.rb +46 -11
- data/lib/puma/cluster.rb +69 -10
- data/lib/puma/configuration.rb +4 -4
- data/lib/puma/const.rb +2 -2
- data/lib/puma/control_cli.rb +12 -5
- data/lib/puma/detect.rb +3 -4
- data/lib/puma/dsl.rb +66 -6
- data/lib/puma/minissl/context_builder.rb +2 -0
- data/lib/puma/minissl.rb +5 -0
- data/lib/puma/null_io.rb +16 -2
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/runner.rb +6 -2
- data/lib/puma/server.rb +73 -23
- data/lib/puma/state_file.rb +2 -2
- data/lib/puma/thread_pool.rb +34 -0
- data/tools/Dockerfile +2 -2
- metadata +3 -3
data/lib/puma/cluster.rb
CHANGED
@@ -85,9 +85,7 @@ module Puma
|
|
85
85
|
@workers << WorkerHandle.new(idx, pid, @phase, @options)
|
86
86
|
end
|
87
87
|
|
88
|
-
if @options[:fork_worker] &&
|
89
|
-
@workers.all? {|x| x.phase == @phase}
|
90
|
-
|
88
|
+
if @options[:fork_worker] && all_workers_in_phase?
|
91
89
|
@fork_writer << "0\n"
|
92
90
|
end
|
93
91
|
end
|
@@ -148,10 +146,22 @@ module Puma
|
|
148
146
|
idx
|
149
147
|
end
|
150
148
|
|
149
|
+
def worker_at(idx)
|
150
|
+
@workers.find { |w| w.index == idx }
|
151
|
+
end
|
152
|
+
|
151
153
|
def all_workers_booted?
|
152
154
|
@workers.count { |w| !w.booted? } == 0
|
153
155
|
end
|
154
156
|
|
157
|
+
def all_workers_in_phase?
|
158
|
+
@workers.all? { |w| w.phase == @phase }
|
159
|
+
end
|
160
|
+
|
161
|
+
def all_workers_idle_timed_out?
|
162
|
+
(@workers.map(&:pid) - idle_timed_out_worker_pids).empty?
|
163
|
+
end
|
164
|
+
|
155
165
|
def check_workers
|
156
166
|
return if @next_check >= Time.now
|
157
167
|
|
@@ -276,7 +286,7 @@ module Puma
|
|
276
286
|
|
277
287
|
# @version 5.0.0
|
278
288
|
def fork_worker!
|
279
|
-
if (worker =
|
289
|
+
if (worker = worker_at 0)
|
280
290
|
worker.phase += 1
|
281
291
|
end
|
282
292
|
phased_restart(true)
|
@@ -338,6 +348,8 @@ module Puma
|
|
338
348
|
def run
|
339
349
|
@status = :run
|
340
350
|
|
351
|
+
@idle_workers = {}
|
352
|
+
|
341
353
|
output_header "cluster"
|
342
354
|
|
343
355
|
# This is aligned with the output from Runner, see Runner#output_header
|
@@ -411,6 +423,8 @@ module Puma
|
|
411
423
|
|
412
424
|
@master_read, @worker_write = read, @wakeup
|
413
425
|
|
426
|
+
@options[:worker_write] = @worker_write
|
427
|
+
|
414
428
|
@config.run_hooks(:before_fork, nil, @log_writer)
|
415
429
|
|
416
430
|
spawn_workers
|
@@ -426,6 +440,11 @@ module Puma
|
|
426
440
|
|
427
441
|
while @status == :run
|
428
442
|
begin
|
443
|
+
if all_workers_idle_timed_out?
|
444
|
+
log "- All workers reached idle timeout"
|
445
|
+
break
|
446
|
+
end
|
447
|
+
|
429
448
|
if @phased_restart
|
430
449
|
start_phased_restart
|
431
450
|
@phased_restart = false
|
@@ -446,7 +465,7 @@ module Puma
|
|
446
465
|
|
447
466
|
if req == "b" || req == "f"
|
448
467
|
pid, idx = result.split(':').map(&:to_i)
|
449
|
-
w =
|
468
|
+
w = worker_at idx
|
450
469
|
w.pid = pid if w.pid.nil?
|
451
470
|
end
|
452
471
|
|
@@ -463,24 +482,37 @@ module Puma
|
|
463
482
|
when "t"
|
464
483
|
w.term unless w.term?
|
465
484
|
when "p"
|
466
|
-
|
485
|
+
status = result.sub(/^\d+/,'').chomp
|
486
|
+
w.ping!(status)
|
467
487
|
@events.fire(:ping!, w)
|
488
|
+
|
489
|
+
if in_phased_restart && workers_not_booted.positive? && w0 = worker_at(0)
|
490
|
+
w0.ping!(status)
|
491
|
+
@events.fire(:ping!, w0)
|
492
|
+
end
|
493
|
+
|
468
494
|
if !booted && @workers.none? {|worker| worker.last_status.empty?}
|
469
495
|
@events.fire_on_booted!
|
470
496
|
debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
|
471
497
|
booted = true
|
472
498
|
end
|
499
|
+
when "i"
|
500
|
+
if @idle_workers[pid]
|
501
|
+
@idle_workers.delete pid
|
502
|
+
else
|
503
|
+
@idle_workers[pid] = true
|
504
|
+
end
|
473
505
|
end
|
474
506
|
else
|
475
507
|
log "! Out-of-sync worker list, no #{pid} worker"
|
476
508
|
end
|
477
509
|
end
|
510
|
+
|
478
511
|
if in_phased_restart && workers_not_booted.zero?
|
479
512
|
@events.fire_on_booted!
|
480
513
|
debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
|
481
514
|
in_phased_restart = false
|
482
515
|
end
|
483
|
-
|
484
516
|
rescue Interrupt
|
485
517
|
@status = :stop
|
486
518
|
end
|
@@ -509,10 +541,28 @@ module Puma
|
|
509
541
|
# loops thru @workers, removing workers that exited, and calling
|
510
542
|
# `#term` if needed
|
511
543
|
def wait_workers
|
544
|
+
# Reap all children, known workers or otherwise.
|
545
|
+
# If puma has PID 1, as it's common in containerized environments,
|
546
|
+
# then it's responsible for reaping orphaned processes, so we must reap
|
547
|
+
# all our dead children, regardless of whether they are workers we spawned
|
548
|
+
# or some reattached processes.
|
549
|
+
reaped_children = {}
|
550
|
+
loop do
|
551
|
+
begin
|
552
|
+
pid, status = Process.wait2(-1, Process::WNOHANG)
|
553
|
+
break unless pid
|
554
|
+
reaped_children[pid] = status
|
555
|
+
rescue Errno::ECHILD
|
556
|
+
break
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
512
560
|
@workers.reject! do |w|
|
513
561
|
next false if w.pid.nil?
|
514
562
|
begin
|
515
|
-
|
563
|
+
# When `fork_worker` is enabled, some worker may not be direct children, but grand children.
|
564
|
+
# Because of this they won't be reaped by `Process.wait2(-1)`, so we need to check them individually)
|
565
|
+
if reaped_children.delete(w.pid) || (@options[:fork_worker] && Process.wait(w.pid, Process::WNOHANG))
|
516
566
|
true
|
517
567
|
else
|
518
568
|
w.term if w.term?
|
@@ -529,6 +579,11 @@ module Puma
|
|
529
579
|
end
|
530
580
|
end
|
531
581
|
end
|
582
|
+
|
583
|
+
# Log unknown children
|
584
|
+
reaped_children.each do |pid, status|
|
585
|
+
log "! reaped unknown child process pid=#{pid} status=#{status}"
|
586
|
+
end
|
532
587
|
end
|
533
588
|
|
534
589
|
# @version 5.0.0
|
@@ -536,14 +591,18 @@ module Puma
|
|
536
591
|
@workers.each do |w|
|
537
592
|
if !w.term? && w.ping_timeout <= Time.now
|
538
593
|
details = if w.booted?
|
539
|
-
"(
|
594
|
+
"(Worker #{w.index} failed to check in within #{@options[:worker_timeout]} seconds)"
|
540
595
|
else
|
541
|
-
"(
|
596
|
+
"(Worker #{w.index} failed to boot within #{@options[:worker_boot_timeout]} seconds)"
|
542
597
|
end
|
543
598
|
log "! Terminating timed out worker #{details}: #{w.pid}"
|
544
599
|
w.kill
|
545
600
|
end
|
546
601
|
end
|
547
602
|
end
|
603
|
+
|
604
|
+
def idle_timed_out_worker_pids
|
605
|
+
@idle_workers.keys
|
606
|
+
end
|
548
607
|
end
|
549
608
|
end
|
data/lib/puma/configuration.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require_relative 'rack/builder'
|
4
4
|
require_relative 'plugin'
|
5
5
|
require_relative 'const'
|
6
|
-
|
6
|
+
require_relative 'dsl'
|
7
7
|
|
8
8
|
module Puma
|
9
9
|
# A class used for storing "leveled" configuration options.
|
@@ -133,8 +133,10 @@ module Puma
|
|
133
133
|
debug: false,
|
134
134
|
early_hints: nil,
|
135
135
|
environment: 'development'.freeze,
|
136
|
-
# Number of seconds to wait until we get the first data for the request
|
136
|
+
# Number of seconds to wait until we get the first data for the request.
|
137
137
|
first_data_timeout: 30,
|
138
|
+
# Number of seconds to wait until the next request before shutting down.
|
139
|
+
idle_timeout: nil,
|
138
140
|
io_selector_backend: :auto,
|
139
141
|
log_requests: false,
|
140
142
|
logger: STDOUT,
|
@@ -385,5 +387,3 @@ module Puma
|
|
385
387
|
end
|
386
388
|
end
|
387
389
|
end
|
388
|
-
|
389
|
-
require_relative 'dsl'
|
data/lib/puma/const.rb
CHANGED
@@ -100,8 +100,8 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "6.
|
104
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "6.4.2"
|
104
|
+
CODE_NAME = "The Eagle of Durango"
|
105
105
|
|
106
106
|
PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
|
107
107
|
|
data/lib/puma/control_cli.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'optparse'
|
4
|
-
require_relative 'state_file'
|
5
4
|
require_relative 'const'
|
6
5
|
require_relative 'detect'
|
7
|
-
require_relative 'configuration'
|
8
6
|
require 'uri'
|
9
7
|
require 'socket'
|
10
8
|
|
@@ -126,6 +124,9 @@ module Puma
|
|
126
124
|
end
|
127
125
|
|
128
126
|
if @config_file
|
127
|
+
require_relative 'configuration'
|
128
|
+
require_relative 'log_writer'
|
129
|
+
|
129
130
|
config = Puma::Configuration.new({ config_files: [@config_file] }, {})
|
130
131
|
config.load
|
131
132
|
@state ||= config.options[:state]
|
@@ -149,6 +150,8 @@ module Puma
|
|
149
150
|
raise "State file not found: #{@state}"
|
150
151
|
end
|
151
152
|
|
153
|
+
require_relative 'state_file'
|
154
|
+
|
152
155
|
sf = Puma::StateFile.new
|
153
156
|
sf.load @state
|
154
157
|
|
@@ -164,22 +167,26 @@ module Puma
|
|
164
167
|
def send_request
|
165
168
|
uri = URI.parse @control_url
|
166
169
|
|
170
|
+
host = uri.host
|
171
|
+
|
167
172
|
# create server object by scheme
|
168
173
|
server =
|
169
174
|
case uri.scheme
|
170
175
|
when 'ssl'
|
171
176
|
require 'openssl'
|
177
|
+
host = host[1..-2] if host&.start_with? '['
|
172
178
|
OpenSSL::SSL::SSLSocket.new(
|
173
|
-
TCPSocket.new(
|
179
|
+
TCPSocket.new(host, uri.port),
|
174
180
|
OpenSSL::SSL::SSLContext.new)
|
175
181
|
.tap { |ssl| ssl.sync_close = true } # default is false
|
176
182
|
.tap(&:connect)
|
177
183
|
when 'tcp'
|
178
|
-
|
184
|
+
host = host[1..-2] if host&.start_with? '['
|
185
|
+
TCPSocket.new host, uri.port
|
179
186
|
when 'unix'
|
180
187
|
# check for abstract UNIXSocket
|
181
188
|
UNIXSocket.new(@control_url.start_with?('unix://@') ?
|
182
|
-
"\0#{
|
189
|
+
"\0#{host}#{uri.path}" : "#{host}#{uri.path}")
|
183
190
|
else
|
184
191
|
raise "Invalid scheme: #{uri.scheme}"
|
185
192
|
end
|
data/lib/puma/detect.rb
CHANGED
@@ -12,15 +12,14 @@ module Puma
|
|
12
12
|
|
13
13
|
IS_JRUBY = Object.const_defined? :JRUBY_VERSION
|
14
14
|
|
15
|
-
IS_OSX =
|
15
|
+
IS_OSX = RUBY_DESCRIPTION.include? 'darwin'
|
16
16
|
|
17
|
-
IS_WINDOWS =
|
18
|
-
IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
|
17
|
+
IS_WINDOWS = RUBY_DESCRIPTION.match?(/mswin|ming|cygwin/)
|
19
18
|
|
20
19
|
IS_LINUX = !(IS_OSX || IS_WINDOWS)
|
21
20
|
|
22
21
|
# @version 5.2.0
|
23
|
-
IS_MRI =
|
22
|
+
IS_MRI = RUBY_ENGINE == 'ruby'
|
24
23
|
|
25
24
|
def self.jruby?
|
26
25
|
IS_JRUBY
|
data/lib/puma/dsl.rb
CHANGED
@@ -315,16 +315,22 @@ module Puma
|
|
315
315
|
bind URI::Generic.build(scheme: 'tcp', host: host, port: Integer(port)).to_s
|
316
316
|
end
|
317
317
|
|
318
|
+
# Define how long the tcp socket stays open, if no data has been received.
|
319
|
+
# @see Puma::Server.new
|
320
|
+
def first_data_timeout(seconds)
|
321
|
+
@options[:first_data_timeout] = Integer(seconds)
|
322
|
+
end
|
323
|
+
|
318
324
|
# Define how long persistent connections can be idle before Puma closes them.
|
319
325
|
# @see Puma::Server.new
|
320
326
|
def persistent_timeout(seconds)
|
321
327
|
@options[:persistent_timeout] = Integer(seconds)
|
322
328
|
end
|
323
329
|
|
324
|
-
#
|
330
|
+
# If a new request is not received within this number of seconds, begin shutting down.
|
325
331
|
# @see Puma::Server.new
|
326
|
-
def
|
327
|
-
@options[:
|
332
|
+
def idle_timeout(seconds)
|
333
|
+
@options[:idle_timeout] = Integer(seconds)
|
328
334
|
end
|
329
335
|
|
330
336
|
# Work around leaky apps that leave garbage in Thread locals
|
@@ -510,6 +516,12 @@ module Puma
|
|
510
516
|
# `true`, which sets reuse 'on' with default values, or a hash, with `:size`
|
511
517
|
# and/or `:timeout` keys, each with integer values.
|
512
518
|
#
|
519
|
+
# The `cert:` options hash parameter can be the path to a certificate
|
520
|
+
# file including all intermediate certificates in PEM format.
|
521
|
+
#
|
522
|
+
# The `cert_pem:` options hash parameter can be String containing the
|
523
|
+
# cerificate and all intermediate certificates in PEM format.
|
524
|
+
#
|
513
525
|
# @example
|
514
526
|
# ssl_bind '127.0.0.1', '9292', {
|
515
527
|
# cert: path_to_cert,
|
@@ -717,6 +729,51 @@ module Puma
|
|
717
729
|
process_hook :before_refork, key, block, 'on_refork'
|
718
730
|
end
|
719
731
|
|
732
|
+
# Provide a block to be executed just before a thread is added to the thread
|
733
|
+
# pool. Be careful: while the block executes, thread creation is delayed, and
|
734
|
+
# probably a request will have to wait too! The new thread will not be added to
|
735
|
+
# the threadpool until the provided block returns.
|
736
|
+
#
|
737
|
+
# Return values are ignored.
|
738
|
+
# Raising an exception will log a warning.
|
739
|
+
#
|
740
|
+
# This hook is useful for doing something when the thread pool grows.
|
741
|
+
#
|
742
|
+
# This can be called multiple times to add several hooks.
|
743
|
+
#
|
744
|
+
# @example
|
745
|
+
# on_thread_start do
|
746
|
+
# puts 'On thread start...'
|
747
|
+
# end
|
748
|
+
def on_thread_start(&block)
|
749
|
+
@options[:before_thread_start] ||= []
|
750
|
+
@options[:before_thread_start] << block
|
751
|
+
end
|
752
|
+
|
753
|
+
# Provide a block to be executed after a thread is trimmed from the thread
|
754
|
+
# pool. Be careful: while this block executes, Puma's main loop is
|
755
|
+
# blocked, so no new requests will be picked up.
|
756
|
+
#
|
757
|
+
# This hook only runs when a thread in the threadpool is trimmed by Puma.
|
758
|
+
# It does not run when a thread dies due to exceptions or any other cause.
|
759
|
+
#
|
760
|
+
# Return values are ignored.
|
761
|
+
# Raising an exception will log a warning.
|
762
|
+
#
|
763
|
+
# This hook is useful for cleaning up thread local resources when a thread
|
764
|
+
# is trimmed.
|
765
|
+
#
|
766
|
+
# This can be called multiple times to add several hooks.
|
767
|
+
#
|
768
|
+
# @example
|
769
|
+
# on_thread_exit do
|
770
|
+
# puts 'On thread exit...'
|
771
|
+
# end
|
772
|
+
def on_thread_exit(&block)
|
773
|
+
@options[:before_thread_exit] ||= []
|
774
|
+
@options[:before_thread_exit] << block
|
775
|
+
end
|
776
|
+
|
720
777
|
# Code to run out-of-band when the worker is idle.
|
721
778
|
# These hooks run immediately after a request has finished
|
722
779
|
# processing and there are no busy threads on the worker.
|
@@ -848,7 +905,8 @@ module Puma
|
|
848
905
|
# not a request timeout, it is to protect against a hung or dead process.
|
849
906
|
# Setting this value will not protect against slow requests.
|
850
907
|
#
|
851
|
-
#
|
908
|
+
# This value must be greater than worker_check_interval.
|
909
|
+
# The default value is 60 seconds.
|
852
910
|
#
|
853
911
|
# @note Cluster mode only.
|
854
912
|
# @example
|
@@ -1132,8 +1190,10 @@ module Puma
|
|
1132
1190
|
|
1133
1191
|
def warn_if_in_single_mode(hook_name)
|
1134
1192
|
return if @options[:silence_fork_callback_warning]
|
1135
|
-
|
1136
|
-
|
1193
|
+
# user_options (CLI) have precedence over config file
|
1194
|
+
workers_val = @config.options.user_options[:workers] || @options[:workers] ||
|
1195
|
+
@config.puma_default_options[:workers] || 0
|
1196
|
+
if workers_val == 0
|
1137
1197
|
log_string =
|
1138
1198
|
"Warning: You specified code to run in a `#{hook_name}` block, " \
|
1139
1199
|
"but Puma is not configured to run in cluster mode (worker count > 0 ), " \
|
data/lib/puma/minissl.rb
CHANGED
@@ -184,6 +184,11 @@ module Puma
|
|
184
184
|
@socket.peeraddr
|
185
185
|
end
|
186
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]
|
187
192
|
# @!attribute [r] peercert
|
188
193
|
def peercert
|
189
194
|
return @peercert if @peercert
|
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
|
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/runner.rb
CHANGED
@@ -70,12 +70,16 @@ module Puma
|
|
70
70
|
|
71
71
|
app = Puma::App::Status.new @launcher, token
|
72
72
|
|
73
|
-
# A Reactor is not created
|
73
|
+
# A Reactor is not created and nio4r is not loaded when 'queue_requests: false'
|
74
74
|
# Use `nil` for events, no hooks in control server
|
75
75
|
control = Puma::Server.new app, nil,
|
76
76
|
{ min_threads: 0, max_threads: 1, queue_requests: false, log_writer: @log_writer }
|
77
77
|
|
78
|
-
|
78
|
+
begin
|
79
|
+
control.binder.parse [str], nil, 'Starting control server'
|
80
|
+
rescue Errno::EADDRINUSE, Errno::EACCES => e
|
81
|
+
raise e, "Error: Control server address '#{str}' is already in use. Original error: #{e.message}"
|
82
|
+
end
|
79
83
|
|
80
84
|
control.run thread_name: 'ctl'
|
81
85
|
@control = control
|
data/lib/puma/server.rb
CHANGED
@@ -15,7 +15,6 @@ require_relative 'request'
|
|
15
15
|
|
16
16
|
require 'socket'
|
17
17
|
require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
|
18
|
-
require 'forwardable'
|
19
18
|
|
20
19
|
module Puma
|
21
20
|
|
@@ -32,7 +31,6 @@ module Puma
|
|
32
31
|
class Server
|
33
32
|
include Puma::Const
|
34
33
|
include Request
|
35
|
-
extend Forwardable
|
36
34
|
|
37
35
|
attr_reader :thread
|
38
36
|
attr_reader :log_writer
|
@@ -48,9 +46,6 @@ module Puma
|
|
48
46
|
attr_accessor :app
|
49
47
|
attr_accessor :binder
|
50
48
|
|
51
|
-
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
|
52
|
-
:add_unix_listener, :connected_ports
|
53
|
-
|
54
49
|
THREAD_LOCAL_KEY = :puma_server
|
55
50
|
|
56
51
|
# Create a server for the rack app +app+.
|
@@ -86,15 +81,18 @@ module Puma
|
|
86
81
|
UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
|
87
82
|
end
|
88
83
|
|
89
|
-
@
|
90
|
-
@
|
91
|
-
@
|
92
|
-
@
|
93
|
-
@
|
94
|
-
@persistent_timeout
|
95
|
-
@
|
96
|
-
@
|
97
|
-
@
|
84
|
+
@clustered = (@options.fetch :workers, 0) > 0
|
85
|
+
@worker_write = @options[:worker_write]
|
86
|
+
@log_writer = @options.fetch :log_writer, LogWriter.stdio
|
87
|
+
@early_hints = @options[:early_hints]
|
88
|
+
@first_data_timeout = @options[:first_data_timeout]
|
89
|
+
@persistent_timeout = @options[:persistent_timeout]
|
90
|
+
@idle_timeout = @options[:idle_timeout]
|
91
|
+
@min_threads = @options[:min_threads]
|
92
|
+
@max_threads = @options[:max_threads]
|
93
|
+
@queue_requests = @options[:queue_requests]
|
94
|
+
@max_fast_inline = @options[:max_fast_inline]
|
95
|
+
@io_selector_backend = @options[:io_selector_backend]
|
98
96
|
@http_content_length_limit = @options[:http_content_length_limit]
|
99
97
|
|
100
98
|
# make this a hash, since we prefer `key?` over `include?`
|
@@ -121,6 +119,8 @@ module Puma
|
|
121
119
|
@precheck_closing = true
|
122
120
|
|
123
121
|
@requests_count = 0
|
122
|
+
|
123
|
+
@idle_timeout_reached = false
|
124
124
|
end
|
125
125
|
|
126
126
|
def inherit_binder(bind)
|
@@ -330,8 +330,28 @@ module Puma
|
|
330
330
|
|
331
331
|
while @status == :run || (drain && shutting_down?)
|
332
332
|
begin
|
333
|
-
ios = IO.select sockets, nil, nil, (shutting_down? ? 0 :
|
334
|
-
|
333
|
+
ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
|
334
|
+
unless ios
|
335
|
+
unless shutting_down?
|
336
|
+
@idle_timeout_reached = true
|
337
|
+
|
338
|
+
if @clustered
|
339
|
+
@worker_write << "i#{Process.pid}\n" rescue nil
|
340
|
+
next
|
341
|
+
else
|
342
|
+
@log_writer.log "- Idle timeout reached"
|
343
|
+
@status = :stop
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
break
|
348
|
+
end
|
349
|
+
|
350
|
+
if @idle_timeout_reached && @clustered
|
351
|
+
@idle_timeout_reached = false
|
352
|
+
@worker_write << "i#{Process.pid}\n" rescue nil
|
353
|
+
end
|
354
|
+
|
335
355
|
ios.first.each do |sock|
|
336
356
|
if sock == check
|
337
357
|
break if handle_check
|
@@ -367,6 +387,7 @@ module Puma
|
|
367
387
|
@queue_requests = false
|
368
388
|
@reactor.shutdown
|
369
389
|
end
|
390
|
+
|
370
391
|
graceful_shutdown if @status == :stop || @status == :restart
|
371
392
|
rescue Exception => e
|
372
393
|
@log_writer.unknown_error e, nil, "Exception handling servers"
|
@@ -476,7 +497,7 @@ module Puma
|
|
476
497
|
end
|
477
498
|
true
|
478
499
|
rescue StandardError => e
|
479
|
-
client_error(e, client)
|
500
|
+
client_error(e, client, requests)
|
480
501
|
# The ensure tries to close +client+ down
|
481
502
|
requests > 0
|
482
503
|
ensure
|
@@ -504,22 +525,22 @@ module Puma
|
|
504
525
|
# :nocov:
|
505
526
|
|
506
527
|
# Handle various error types thrown by Client I/O operations.
|
507
|
-
def client_error(e, client)
|
528
|
+
def client_error(e, client, requests = 1)
|
508
529
|
# Swallow, do not log
|
509
530
|
return if [ConnectionError, EOFError].include?(e.class)
|
510
531
|
|
511
|
-
lowlevel_error(e, client.env)
|
512
532
|
case e
|
513
533
|
when MiniSSL::SSLError
|
534
|
+
lowlevel_error(e, client.env)
|
514
535
|
@log_writer.ssl_error e, client.io
|
515
536
|
when HttpParserError
|
516
|
-
client
|
537
|
+
response_to_error(client, requests, e, 400)
|
517
538
|
@log_writer.parse_error e, client
|
518
539
|
when HttpParserError501
|
519
|
-
client
|
540
|
+
response_to_error(client, requests, e, 501)
|
520
541
|
@log_writer.parse_error e, client
|
521
542
|
else
|
522
|
-
client
|
543
|
+
response_to_error(client, requests, e, 500)
|
523
544
|
@log_writer.unknown_error e, nil, "Read"
|
524
545
|
end
|
525
546
|
end
|
@@ -541,10 +562,17 @@ module Puma
|
|
541
562
|
backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
|
542
563
|
[status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
|
543
564
|
else
|
544
|
-
[status, {}, ["
|
565
|
+
[status, {}, [""]]
|
545
566
|
end
|
546
567
|
end
|
547
568
|
|
569
|
+
def response_to_error(client, requests, err, status_code)
|
570
|
+
status, headers, res_body = lowlevel_error(err, client.env, status_code)
|
571
|
+
prepare_response(status, headers, res_body, requests, client)
|
572
|
+
client.write_error(status_code)
|
573
|
+
end
|
574
|
+
private :response_to_error
|
575
|
+
|
548
576
|
# Wait for all outstanding requests to finish.
|
549
577
|
#
|
550
578
|
def graceful_shutdown
|
@@ -623,5 +651,27 @@ module Puma
|
|
623
651
|
def stats
|
624
652
|
STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
|
625
653
|
end
|
654
|
+
|
655
|
+
# below are 'delegations' to binder
|
656
|
+
# remove in Puma 7?
|
657
|
+
|
658
|
+
|
659
|
+
def add_tcp_listener(host, port, optimize_for_latency = true, backlog = 1024)
|
660
|
+
@binder.add_tcp_listener host, port, optimize_for_latency, backlog
|
661
|
+
end
|
662
|
+
|
663
|
+
def add_ssl_listener(host, port, ctx, optimize_for_latency = true,
|
664
|
+
backlog = 1024)
|
665
|
+
@binder.add_ssl_listener host, port, ctx, optimize_for_latency, backlog
|
666
|
+
end
|
667
|
+
|
668
|
+
def add_unix_listener(path, umask = nil, mode = nil, backlog = 1024)
|
669
|
+
@binder.add_unix_listener path, umask, mode, backlog
|
670
|
+
end
|
671
|
+
|
672
|
+
# @!attribute [r] connected_ports
|
673
|
+
def connected_ports
|
674
|
+
@binder.connected_ports
|
675
|
+
end
|
626
676
|
end
|
627
677
|
end
|