puma 3.8.2 → 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 +5 -5
- data/History.md +157 -0
- data/README.md +155 -225
- data/docs/architecture.md +37 -0
- data/{DEPLOYMENT.md → 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 +28 -0
- data/docs/restart.md +41 -0
- data/docs/signals.md +56 -3
- data/docs/systemd.md +130 -37
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/http11_parser.c +84 -84
- data/ext/puma_http11/http11_parser.rl +9 -9
- data/ext/puma_http11/mini_ssl.c +51 -9
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +26 -6
- data/lib/puma.rb +8 -0
- data/lib/puma/app/status.rb +9 -0
- data/lib/puma/binder.rb +31 -18
- data/lib/puma/cli.rb +22 -7
- data/lib/puma/client.rb +67 -18
- data/lib/puma/cluster.rb +64 -19
- data/lib/puma/commonlogger.rb +2 -0
- data/lib/puma/configuration.rb +22 -14
- data/lib/puma/const.rb +13 -2
- data/lib/puma/control_cli.rb +26 -14
- 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 +91 -12
- data/lib/puma/events.rb +3 -2
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher.rb +51 -30
- data/lib/puma/minissl.rb +79 -28
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin.rb +2 -0
- data/lib/puma/plugin/tmp_restart.rb +0 -1
- data/lib/puma/rack/builder.rb +2 -1
- data/lib/puma/reactor.rb +218 -30
- data/lib/puma/runner.rb +17 -4
- data/lib/puma/server.rb +113 -49
- data/lib/puma/single.rb +16 -5
- data/lib/puma/state_file.rb +2 -0
- data/lib/puma/tcp_logger.rb +2 -0
- data/lib/puma/thread_pool.rb +59 -6
- data/lib/puma/util.rb +2 -6
- data/lib/rack/handler/puma.rb +13 -2
- data/tools/jungle/README.md +12 -2
- data/tools/jungle/init.d/README.md +2 -0
- data/tools/jungle/init.d/puma +7 -7
- data/tools/jungle/init.d/run-puma +1 -1
- data/tools/jungle/rc.d/README.md +74 -0
- data/tools/jungle/rc.d/puma +61 -0
- data/tools/jungle/rc.d/puma.conf +10 -0
- data/tools/trickletest.rb +1 -1
- metadata +25 -87
- data/.github/issue_template.md +0 -20
- data/Gemfile +0 -12
- data/Manifest.txt +0 -78
- data/Rakefile +0 -158
- data/Release.md +0 -9
- data/gemfiles/2.1-Gemfile +0 -12
- 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/puma.gemspec +0 -52
data/lib/puma/runner.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/server'
|
2
4
|
require 'puma/const'
|
3
5
|
|
4
6
|
module Puma
|
7
|
+
# Generic class that is used by `Puma::Cluster` and `Puma::Single` to
|
8
|
+
# serve requests. This class spawns a new instance of `Puma::Server` via
|
9
|
+
# a call to `start_server`.
|
5
10
|
class Runner
|
6
11
|
def initialize(cli, events)
|
7
12
|
@launcher = cli
|
@@ -19,6 +24,10 @@ module Puma
|
|
19
24
|
@options[:environment] == "development"
|
20
25
|
end
|
21
26
|
|
27
|
+
def test?
|
28
|
+
@options[:environment] == "test"
|
29
|
+
end
|
30
|
+
|
22
31
|
def log(str)
|
23
32
|
@events.log str
|
24
33
|
end
|
@@ -46,7 +55,7 @@ module Puma
|
|
46
55
|
app = Puma::App::Status.new @launcher
|
47
56
|
|
48
57
|
if token = @options[:control_auth_token]
|
49
|
-
app.auth_token = token unless token.empty?
|
58
|
+
app.auth_token = token unless token.empty? || token == 'none'
|
50
59
|
end
|
51
60
|
|
52
61
|
control = Puma::Server.new app, @launcher.events
|
@@ -107,7 +116,7 @@ module Puma
|
|
107
116
|
append = @options[:redirect_append]
|
108
117
|
|
109
118
|
if stdout
|
110
|
-
unless Dir.
|
119
|
+
unless Dir.exist?(File.dirname(stdout))
|
111
120
|
raise "Cannot redirect STDOUT to #{stdout}"
|
112
121
|
end
|
113
122
|
|
@@ -117,7 +126,7 @@ module Puma
|
|
117
126
|
end
|
118
127
|
|
119
128
|
if stderr
|
120
|
-
unless Dir.
|
129
|
+
unless Dir.exist?(File.dirname(stderr))
|
121
130
|
raise "Cannot redirect STDERR to #{stderr}"
|
122
131
|
end
|
123
132
|
|
@@ -161,7 +170,11 @@ module Puma
|
|
161
170
|
server.tcp_mode!
|
162
171
|
end
|
163
172
|
|
164
|
-
|
173
|
+
if @options[:early_hints]
|
174
|
+
server.early_hints = true
|
175
|
+
end
|
176
|
+
|
177
|
+
unless development? || test?
|
165
178
|
server.leak_stack_on_error = false
|
166
179
|
end
|
167
180
|
|
data/lib/puma/server.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'stringio'
|
2
4
|
|
3
5
|
require 'puma/thread_pool'
|
4
6
|
require 'puma/const'
|
5
7
|
require 'puma/events'
|
6
8
|
require 'puma/null_io'
|
7
|
-
require 'puma/compat'
|
8
9
|
require 'puma/reactor'
|
9
10
|
require 'puma/client'
|
10
11
|
require 'puma/binder'
|
@@ -14,15 +15,20 @@ require 'puma/util'
|
|
14
15
|
|
15
16
|
require 'puma/puma_http11'
|
16
17
|
|
17
|
-
unless Puma.const_defined? "IOBuffer"
|
18
|
-
require 'puma/io_buffer'
|
19
|
-
end
|
20
|
-
|
21
18
|
require 'socket'
|
22
19
|
|
23
20
|
module Puma
|
24
21
|
|
25
22
|
# The HTTP Server itself. Serves out a single Rack app.
|
23
|
+
#
|
24
|
+
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
25
|
+
# to generate one or more `Puma::Server` instances capable of handling requests.
|
26
|
+
# Each Puma process will contain one `Puma::Server` instance.
|
27
|
+
#
|
28
|
+
# The `Puma::Server` instance pulls requests from the socket, adds them to a
|
29
|
+
# `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
|
30
|
+
#
|
31
|
+
# Each `Puma::Server` will have one reactor and one thread pool.
|
26
32
|
class Server
|
27
33
|
|
28
34
|
include Puma::Const
|
@@ -62,13 +68,12 @@ module Puma
|
|
62
68
|
|
63
69
|
@thread = nil
|
64
70
|
@thread_pool = nil
|
71
|
+
@early_hints = nil
|
65
72
|
|
66
73
|
@persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
|
74
|
+
@first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
|
67
75
|
|
68
76
|
@binder = Binder.new(events)
|
69
|
-
@own_binder = true
|
70
|
-
|
71
|
-
@first_data_timeout = FIRST_DATA_TIMEOUT
|
72
77
|
|
73
78
|
@leak_stack_on_error = true
|
74
79
|
|
@@ -82,7 +87,7 @@ module Puma
|
|
82
87
|
@precheck_closing = true
|
83
88
|
end
|
84
89
|
|
85
|
-
attr_accessor :binder, :leak_stack_on_error
|
90
|
+
attr_accessor :binder, :leak_stack_on_error, :early_hints
|
86
91
|
|
87
92
|
forward :add_tcp_listener, :@binder
|
88
93
|
forward :add_ssl_listener, :@binder
|
@@ -91,7 +96,6 @@ module Puma
|
|
91
96
|
|
92
97
|
def inherit_binder(bind)
|
93
98
|
@binder = bind
|
94
|
-
@own_binder = false
|
95
99
|
end
|
96
100
|
|
97
101
|
def tcp_mode!
|
@@ -111,6 +115,7 @@ module Puma
|
|
111
115
|
begin
|
112
116
|
socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
|
113
117
|
rescue IOError, SystemCallError
|
118
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
114
119
|
end
|
115
120
|
end
|
116
121
|
|
@@ -118,6 +123,7 @@ module Puma
|
|
118
123
|
begin
|
119
124
|
socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
|
120
125
|
rescue IOError, SystemCallError
|
126
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
121
127
|
end
|
122
128
|
end
|
123
129
|
|
@@ -128,6 +134,7 @@ module Puma
|
|
128
134
|
begin
|
129
135
|
tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
|
130
136
|
rescue IOError, SystemCallError
|
137
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
131
138
|
@precheck_closing = false
|
132
139
|
false
|
133
140
|
else
|
@@ -156,6 +163,18 @@ module Puma
|
|
156
163
|
@thread_pool and @thread_pool.spawned
|
157
164
|
end
|
158
165
|
|
166
|
+
|
167
|
+
# This number represents the number of requests that
|
168
|
+
# the server is capable of taking right now.
|
169
|
+
#
|
170
|
+
# For example if the number is 5 then it means
|
171
|
+
# there are 5 threads sitting idle ready to take
|
172
|
+
# a request. If one request comes in, then the
|
173
|
+
# value would be 4 until it finishes processing.
|
174
|
+
def pool_capacity
|
175
|
+
@thread_pool and @thread_pool.pool_capacity
|
176
|
+
end
|
177
|
+
|
159
178
|
# Lopez Mode == raw tcp apps
|
160
179
|
|
161
180
|
def run_lopez_mode(background=true)
|
@@ -217,7 +236,11 @@ module Puma
|
|
217
236
|
# nothing
|
218
237
|
rescue Errno::ECONNABORTED
|
219
238
|
# client closed the socket even before accept
|
220
|
-
|
239
|
+
begin
|
240
|
+
io.close
|
241
|
+
rescue
|
242
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
243
|
+
end
|
221
244
|
end
|
222
245
|
end
|
223
246
|
end
|
@@ -234,11 +257,17 @@ module Puma
|
|
234
257
|
STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
|
235
258
|
STDERR.puts e.backtrace
|
236
259
|
ensure
|
237
|
-
|
238
|
-
|
260
|
+
begin
|
261
|
+
@check.close
|
262
|
+
rescue
|
263
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
264
|
+
end
|
239
265
|
|
240
|
-
|
241
|
-
|
266
|
+
# Prevent can't modify frozen IOError (RuntimeError)
|
267
|
+
begin
|
268
|
+
@notify.close
|
269
|
+
rescue IOError
|
270
|
+
# no biggy
|
242
271
|
end
|
243
272
|
end
|
244
273
|
|
@@ -292,7 +321,7 @@ module Puma
|
|
292
321
|
client.close
|
293
322
|
|
294
323
|
@events.parse_error self, client.env, e
|
295
|
-
rescue ConnectionError
|
324
|
+
rescue ConnectionError, EOFError
|
296
325
|
client.close
|
297
326
|
else
|
298
327
|
if process_now
|
@@ -363,13 +392,20 @@ module Puma
|
|
363
392
|
end
|
364
393
|
|
365
394
|
pool << client
|
366
|
-
pool.wait_until_not_full
|
395
|
+
busy_threads = pool.wait_until_not_full
|
396
|
+
if busy_threads == 0
|
397
|
+
@options[:out_of_band].each(&:call) if @options[:out_of_band]
|
398
|
+
end
|
367
399
|
end
|
368
400
|
rescue SystemCallError
|
369
401
|
# nothing
|
370
402
|
rescue Errno::ECONNABORTED
|
371
403
|
# client closed the socket even before accept
|
372
|
-
|
404
|
+
begin
|
405
|
+
io.close
|
406
|
+
rescue
|
407
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
408
|
+
end
|
373
409
|
end
|
374
410
|
end
|
375
411
|
end
|
@@ -391,10 +427,6 @@ module Puma
|
|
391
427
|
ensure
|
392
428
|
@check.close
|
393
429
|
@notify.close
|
394
|
-
|
395
|
-
if @status != :restart and @own_binder
|
396
|
-
@binder.close
|
397
|
-
end
|
398
430
|
end
|
399
431
|
|
400
432
|
@events.fire :state, :done
|
@@ -491,6 +523,7 @@ module Puma
|
|
491
523
|
begin
|
492
524
|
client.close if close_socket
|
493
525
|
rescue IOError, SystemCallError
|
526
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
494
527
|
# Already closed
|
495
528
|
rescue StandardError => e
|
496
529
|
@events.unknown_error self, e, "Client"
|
@@ -522,7 +555,9 @@ module Puma
|
|
522
555
|
|
523
556
|
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
524
557
|
|
525
|
-
env
|
558
|
+
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
559
|
+
# so only set the env value if there actually is a value.
|
560
|
+
env[QUERY_STRING] = uri.query if uri.query
|
526
561
|
end
|
527
562
|
|
528
563
|
env[PATH_INFO] = env[REQUEST_PATH]
|
@@ -557,15 +592,19 @@ module Puma
|
|
557
592
|
env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
|
558
593
|
end
|
559
594
|
|
560
|
-
#
|
561
|
-
#
|
562
|
-
# the rack app. Then construct the response and write it back to
|
563
|
-
# +client+
|
595
|
+
# Takes the request +req+, invokes the Rack application to construct
|
596
|
+
# the response and writes it back to +req.io+.
|
564
597
|
#
|
565
|
-
# +
|
566
|
-
#
|
567
|
-
# it up again.
|
598
|
+
# The second parameter +lines+ is a IO-like object unique to this thread.
|
599
|
+
# This is normally an instance of Puma::IOBuffer.
|
568
600
|
#
|
601
|
+
# It'll return +false+ when the connection is closed, this doesn't mean
|
602
|
+
# that the response wasn't successful.
|
603
|
+
#
|
604
|
+
# It'll return +:async+ if the connection remains open but will be handled
|
605
|
+
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
606
|
+
#
|
607
|
+
# Finally, it'll return +true+ on keep-alive connections.
|
569
608
|
def handle_request(req, lines)
|
570
609
|
env = req.env
|
571
610
|
client = req.io
|
@@ -590,6 +629,24 @@ module Puma
|
|
590
629
|
env[RACK_INPUT] = body
|
591
630
|
env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
|
592
631
|
|
632
|
+
if @early_hints
|
633
|
+
env[EARLY_HINTS] = lambda { |headers|
|
634
|
+
fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
|
635
|
+
|
636
|
+
headers.each_pair do |k, vs|
|
637
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
638
|
+
vs.to_s.split(NEWLINE).each do |v|
|
639
|
+
fast_write client, "#{k}: #{v}\r\n"
|
640
|
+
end
|
641
|
+
else
|
642
|
+
fast_write client, "#{k}: #{vs}\r\n"
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
fast_write client, "\r\n".freeze
|
647
|
+
}
|
648
|
+
end
|
649
|
+
|
593
650
|
# A rack extension. If the app writes #call'ables to this
|
594
651
|
# array, we will invoke them when the request is done.
|
595
652
|
#
|
@@ -687,7 +744,7 @@ module Puma
|
|
687
744
|
next
|
688
745
|
end
|
689
746
|
|
690
|
-
if vs.respond_to?(:to_s)
|
747
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
691
748
|
vs.to_s.split(NEWLINE).each do |v|
|
692
749
|
lines.append k, colon, v, line_ending
|
693
750
|
end
|
@@ -731,8 +788,8 @@ module Puma
|
|
731
788
|
|
732
789
|
begin
|
733
790
|
res_body.each do |part|
|
791
|
+
next if part.bytesize.zero?
|
734
792
|
if chunked
|
735
|
-
next if part.bytesize.zero?
|
736
793
|
fast_write client, part.bytesize.to_s(16)
|
737
794
|
fast_write client, line_ending
|
738
795
|
fast_write client, part
|
@@ -882,6 +939,10 @@ module Puma
|
|
882
939
|
@events.debug "Drained #{count} additional connections."
|
883
940
|
end
|
884
941
|
|
942
|
+
if @status != :restart
|
943
|
+
@binder.close
|
944
|
+
end
|
945
|
+
|
885
946
|
if @thread_pool
|
886
947
|
if timeout = @options[:force_shutdown_after]
|
887
948
|
@thread_pool.shutdown timeout.to_i
|
@@ -891,35 +952,38 @@ module Puma
|
|
891
952
|
end
|
892
953
|
end
|
893
954
|
|
894
|
-
|
895
|
-
# off the request queue before finally exiting.
|
896
|
-
#
|
897
|
-
def stop(sync=false)
|
955
|
+
def notify_safely(message)
|
898
956
|
begin
|
899
|
-
@notify <<
|
957
|
+
@notify << message
|
900
958
|
rescue IOError
|
901
|
-
|
959
|
+
# The server, in another thread, is shutting down
|
960
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
961
|
+
rescue RuntimeError => e
|
962
|
+
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
|
963
|
+
if e.message.include?('IOError')
|
964
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
965
|
+
else
|
966
|
+
raise e
|
967
|
+
end
|
902
968
|
end
|
969
|
+
end
|
970
|
+
private :notify_safely
|
971
|
+
|
972
|
+
# Stops the acceptor thread and then causes the worker threads to finish
|
973
|
+
# off the request queue before finally exiting.
|
903
974
|
|
975
|
+
def stop(sync=false)
|
976
|
+
notify_safely(STOP_COMMAND)
|
904
977
|
@thread.join if @thread && sync
|
905
978
|
end
|
906
979
|
|
907
980
|
def halt(sync=false)
|
908
|
-
|
909
|
-
@notify << HALT_COMMAND
|
910
|
-
rescue IOError
|
911
|
-
# The server, in another thread, is shutting down
|
912
|
-
end
|
913
|
-
|
981
|
+
notify_safely(HALT_COMMAND)
|
914
982
|
@thread.join if @thread && sync
|
915
983
|
end
|
916
984
|
|
917
985
|
def begin_restart
|
918
|
-
|
919
|
-
@notify << RESTART_COMMAND
|
920
|
-
rescue IOError
|
921
|
-
# The server, in another thread, is shutting down
|
922
|
-
end
|
986
|
+
notify_safely(RESTART_COMMAND)
|
923
987
|
end
|
924
988
|
|
925
989
|
def fast_write(io, str)
|
data/lib/puma/single.rb
CHANGED
@@ -1,13 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/runner'
|
2
4
|
require 'puma/detect'
|
3
5
|
require 'puma/plugin'
|
4
6
|
|
5
7
|
module Puma
|
8
|
+
# This class is instantiated by the `Puma::Launcher` and used
|
9
|
+
# to boot and serve a Ruby application when no puma "workers" are needed
|
10
|
+
# i.e. only using "threaded" mode. For example `$ puma -t 1:5`
|
11
|
+
#
|
12
|
+
# At the core of this class is running an instance of `Puma::Server` which
|
13
|
+
# gets created via the `start_server` method from the `Puma::Runner` class
|
14
|
+
# that this inherits from.
|
6
15
|
class Single < Runner
|
7
16
|
def stats
|
8
|
-
b = @server.backlog
|
9
|
-
r = @server.running
|
10
|
-
|
17
|
+
b = @server.backlog || 0
|
18
|
+
r = @server.running || 0
|
19
|
+
t = @server.pool_capacity || 0
|
20
|
+
m = @server.max_threads || 0
|
21
|
+
%Q!{ "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
|
11
22
|
end
|
12
23
|
|
13
24
|
def restart
|
@@ -15,7 +26,7 @@ module Puma
|
|
15
26
|
end
|
16
27
|
|
17
28
|
def stop
|
18
|
-
@server.stop
|
29
|
+
@server.stop(false) if @server
|
19
30
|
end
|
20
31
|
|
21
32
|
def halt
|
@@ -25,7 +36,7 @@ module Puma
|
|
25
36
|
def stop_blocked
|
26
37
|
log "- Gracefully stopping, waiting for requests to finish"
|
27
38
|
@control.stop(true) if @control
|
28
|
-
@server.stop(true)
|
39
|
+
@server.stop(true) if @server
|
29
40
|
end
|
30
41
|
|
31
42
|
def jruby_daemon?
|