puma 6.3.1 → 6.4.2
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 +104 -9
- 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 +31 -3
- 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
|