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/runner.rb
CHANGED
@@ -14,6 +14,7 @@ module Puma
|
|
14
14
|
@options = cli.options
|
15
15
|
@app = nil
|
16
16
|
@control = nil
|
17
|
+
@started_at = Time.now
|
17
18
|
end
|
18
19
|
|
19
20
|
def daemon?
|
@@ -52,12 +53,12 @@ module Puma
|
|
52
53
|
|
53
54
|
uri = URI.parse str
|
54
55
|
|
55
|
-
app = Puma::App::Status.new @launcher
|
56
|
-
|
57
56
|
if token = @options[:control_auth_token]
|
58
|
-
|
57
|
+
token = nil if token.empty? || token == 'none'
|
59
58
|
end
|
60
59
|
|
60
|
+
app = Puma::App::Status.new @launcher, token
|
61
|
+
|
61
62
|
control = Puma::Server.new app, @launcher.events
|
62
63
|
control.min_threads = 0
|
63
64
|
control.max_threads = 1
|
data/lib/puma/server.rb
CHANGED
@@ -6,21 +6,16 @@ require 'puma/thread_pool'
|
|
6
6
|
require 'puma/const'
|
7
7
|
require 'puma/events'
|
8
8
|
require 'puma/null_io'
|
9
|
-
require 'puma/compat'
|
10
9
|
require 'puma/reactor'
|
11
10
|
require 'puma/client'
|
12
11
|
require 'puma/binder'
|
13
|
-
require 'puma/delegation'
|
14
12
|
require 'puma/accept_nonblock'
|
15
13
|
require 'puma/util'
|
16
14
|
|
17
15
|
require 'puma/puma_http11'
|
18
16
|
|
19
|
-
unless Puma.const_defined? "IOBuffer"
|
20
|
-
require 'puma/io_buffer'
|
21
|
-
end
|
22
|
-
|
23
17
|
require 'socket'
|
18
|
+
require 'forwardable'
|
24
19
|
|
25
20
|
module Puma
|
26
21
|
|
@@ -28,7 +23,7 @@ module Puma
|
|
28
23
|
#
|
29
24
|
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
30
25
|
# to generate one or more `Puma::Server` instances capable of handling requests.
|
31
|
-
# Each Puma process will contain one `Puma::Server`
|
26
|
+
# Each Puma process will contain one `Puma::Server` instance.
|
32
27
|
#
|
33
28
|
# The `Puma::Server` instance pulls requests from the socket, adds them to a
|
34
29
|
# `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
|
@@ -37,7 +32,7 @@ module Puma
|
|
37
32
|
class Server
|
38
33
|
|
39
34
|
include Puma::Const
|
40
|
-
extend
|
35
|
+
extend Forwardable
|
41
36
|
|
42
37
|
attr_reader :thread
|
43
38
|
attr_reader :events
|
@@ -79,7 +74,6 @@ module Puma
|
|
79
74
|
@first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
|
80
75
|
|
81
76
|
@binder = Binder.new(events)
|
82
|
-
@own_binder = true
|
83
77
|
|
84
78
|
@leak_stack_on_error = true
|
85
79
|
|
@@ -95,14 +89,10 @@ module Puma
|
|
95
89
|
|
96
90
|
attr_accessor :binder, :leak_stack_on_error, :early_hints
|
97
91
|
|
98
|
-
|
99
|
-
forward :add_ssl_listener, :@binder
|
100
|
-
forward :add_unix_listener, :@binder
|
101
|
-
forward :connected_port, :@binder
|
92
|
+
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
|
102
93
|
|
103
94
|
def inherit_binder(bind)
|
104
95
|
@binder = bind
|
105
|
-
@own_binder = false
|
106
96
|
end
|
107
97
|
|
108
98
|
def tcp_mode!
|
@@ -214,7 +204,10 @@ module Puma
|
|
214
204
|
@events.fire :state, :running
|
215
205
|
|
216
206
|
if background
|
217
|
-
@thread = Thread.new
|
207
|
+
@thread = Thread.new do
|
208
|
+
Puma.set_thread_name "server"
|
209
|
+
handle_servers_lopez_mode
|
210
|
+
end
|
218
211
|
return @thread
|
219
212
|
else
|
220
213
|
handle_servers_lopez_mode
|
@@ -270,10 +263,11 @@ module Puma
|
|
270
263
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
271
264
|
end
|
272
265
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
266
|
+
# Prevent can't modify frozen IOError (RuntimeError)
|
267
|
+
begin
|
268
|
+
@notify.close
|
269
|
+
rescue IOError
|
270
|
+
# no biggy
|
277
271
|
end
|
278
272
|
end
|
279
273
|
|
@@ -323,7 +317,7 @@ module Puma
|
|
323
317
|
|
324
318
|
@events.ssl_error self, addr, cert, e
|
325
319
|
rescue HttpParserError => e
|
326
|
-
client.
|
320
|
+
client.write_error(400)
|
327
321
|
client.close
|
328
322
|
|
329
323
|
@events.parse_error self, client.env, e
|
@@ -357,7 +351,10 @@ module Puma
|
|
357
351
|
@events.fire :state, :running
|
358
352
|
|
359
353
|
if background
|
360
|
-
@thread = Thread.new
|
354
|
+
@thread = Thread.new do
|
355
|
+
Puma.set_thread_name "server"
|
356
|
+
handle_servers
|
357
|
+
end
|
361
358
|
return @thread
|
362
359
|
else
|
363
360
|
handle_servers
|
@@ -398,7 +395,10 @@ module Puma
|
|
398
395
|
end
|
399
396
|
|
400
397
|
pool << client
|
401
|
-
pool.wait_until_not_full
|
398
|
+
busy_threads = pool.wait_until_not_full
|
399
|
+
if busy_threads == 0
|
400
|
+
@options[:out_of_band].each(&:call) if @options[:out_of_band]
|
401
|
+
end
|
402
402
|
end
|
403
403
|
rescue SystemCallError
|
404
404
|
# nothing
|
@@ -430,10 +430,6 @@ module Puma
|
|
430
430
|
ensure
|
431
431
|
@check.close
|
432
432
|
@notify.close
|
433
|
-
|
434
|
-
if @status != :restart and @own_binder
|
435
|
-
@binder.close
|
436
|
-
end
|
437
433
|
end
|
438
434
|
|
439
435
|
@events.fire :state, :done
|
@@ -470,8 +466,6 @@ module Puma
|
|
470
466
|
clean_thread_locals = @options[:clean_thread_locals]
|
471
467
|
close_socket = true
|
472
468
|
|
473
|
-
requests = 0
|
474
|
-
|
475
469
|
while true
|
476
470
|
case handle_request(client, buffer)
|
477
471
|
when false
|
@@ -485,19 +479,7 @@ module Puma
|
|
485
479
|
|
486
480
|
ThreadPool.clean_thread_locals if clean_thread_locals
|
487
481
|
|
488
|
-
|
489
|
-
|
490
|
-
check_for_more_data = @status == :run
|
491
|
-
|
492
|
-
if requests >= MAX_FAST_INLINE
|
493
|
-
# This will mean that reset will only try to use the data it already
|
494
|
-
# has buffered and won't try to read more data. What this means is that
|
495
|
-
# every client, independent of their request speed, gets treated like a slow
|
496
|
-
# one once every MAX_FAST_INLINE requests.
|
497
|
-
check_for_more_data = false
|
498
|
-
end
|
499
|
-
|
500
|
-
unless client.reset(check_for_more_data)
|
482
|
+
unless client.reset(@status == :run)
|
501
483
|
close_socket = false
|
502
484
|
client.set_timeout @persistent_timeout
|
503
485
|
@reactor.add client
|
@@ -526,7 +508,7 @@ module Puma
|
|
526
508
|
rescue HttpParserError => e
|
527
509
|
lowlevel_error(e, client.env)
|
528
510
|
|
529
|
-
client.
|
511
|
+
client.write_error(400)
|
530
512
|
|
531
513
|
@events.parse_error self, client.env, e
|
532
514
|
|
@@ -534,7 +516,7 @@ module Puma
|
|
534
516
|
rescue StandardError => e
|
535
517
|
lowlevel_error(e, client.env)
|
536
518
|
|
537
|
-
client.
|
519
|
+
client.write_error(500)
|
538
520
|
|
539
521
|
@events.unknown_error self, e, "Read"
|
540
522
|
|
@@ -609,19 +591,26 @@ module Puma
|
|
609
591
|
end
|
610
592
|
|
611
593
|
def default_server_port(env)
|
612
|
-
|
613
|
-
|
594
|
+
if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
|
595
|
+
PORT_443
|
596
|
+
else
|
597
|
+
PORT_80
|
598
|
+
end
|
614
599
|
end
|
615
600
|
|
616
|
-
#
|
617
|
-
#
|
618
|
-
#
|
619
|
-
# +
|
601
|
+
# Takes the request +req+, invokes the Rack application to construct
|
602
|
+
# the response and writes it back to +req.io+.
|
603
|
+
#
|
604
|
+
# The second parameter +lines+ is a IO-like object unique to this thread.
|
605
|
+
# This is normally an instance of Puma::IOBuffer.
|
620
606
|
#
|
621
|
-
# +
|
622
|
-
#
|
623
|
-
# it up again.
|
607
|
+
# It'll return +false+ when the connection is closed, this doesn't mean
|
608
|
+
# that the response wasn't successful.
|
624
609
|
#
|
610
|
+
# It'll return +:async+ if the connection remains open but will be handled
|
611
|
+
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
612
|
+
#
|
613
|
+
# Finally, it'll return +true+ on keep-alive connections.
|
625
614
|
def handle_request(req, lines)
|
626
615
|
env = req.env
|
627
616
|
client = req.io
|
@@ -644,23 +633,27 @@ module Puma
|
|
644
633
|
head = env[REQUEST_METHOD] == HEAD
|
645
634
|
|
646
635
|
env[RACK_INPUT] = body
|
647
|
-
env[RACK_URL_SCHEME] =
|
636
|
+
env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
648
637
|
|
649
638
|
if @early_hints
|
650
639
|
env[EARLY_HINTS] = lambda { |headers|
|
651
|
-
|
640
|
+
begin
|
641
|
+
fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
|
652
642
|
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
643
|
+
headers.each_pair do |k, vs|
|
644
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
645
|
+
vs.to_s.split(NEWLINE).each do |v|
|
646
|
+
fast_write client, "#{k}: #{v}\r\n"
|
647
|
+
end
|
648
|
+
else
|
649
|
+
fast_write client, "#{k}: #{vs}\r\n"
|
657
650
|
end
|
658
|
-
else
|
659
|
-
fast_write client, "#{k}: #{vs}\r\n"
|
660
651
|
end
|
661
|
-
end
|
662
652
|
|
663
|
-
|
653
|
+
fast_write client, "\r\n".freeze
|
654
|
+
rescue ConnectionError
|
655
|
+
# noop, if we lost the socket we just won't send the early hints
|
656
|
+
end
|
664
657
|
}
|
665
658
|
end
|
666
659
|
|
@@ -956,6 +949,10 @@ module Puma
|
|
956
949
|
@events.debug "Drained #{count} additional connections."
|
957
950
|
end
|
958
951
|
|
952
|
+
if @status != :restart
|
953
|
+
@binder.close
|
954
|
+
end
|
955
|
+
|
959
956
|
if @thread_pool
|
960
957
|
if timeout = @options[:force_shutdown_after]
|
961
958
|
@thread_pool.shutdown timeout.to_i
|
data/lib/puma/single.rb
CHANGED
@@ -18,7 +18,7 @@ module Puma
|
|
18
18
|
r = @server.running || 0
|
19
19
|
t = @server.pool_capacity || 0
|
20
20
|
m = @server.max_threads || 0
|
21
|
-
%Q!{ "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
|
21
|
+
%Q!{ "started_at": "#{@started_at.utc.iso8601}", "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
|
22
22
|
end
|
23
23
|
|
24
24
|
def restart
|
@@ -26,7 +26,7 @@ module Puma
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def stop
|
29
|
-
@server.stop
|
29
|
+
@server.stop(false) if @server
|
30
30
|
end
|
31
31
|
|
32
32
|
def halt
|
@@ -36,7 +36,7 @@ module Puma
|
|
36
36
|
def stop_blocked
|
37
37
|
log "- Gracefully stopping, waiting for requests to finish"
|
38
38
|
@control.stop(true) if @control
|
39
|
-
@server.stop(true)
|
39
|
+
@server.stop(true) if @server
|
40
40
|
end
|
41
41
|
|
42
42
|
def jruby_daemon?
|
data/lib/puma/thread_pool.rb
CHANGED
@@ -87,8 +87,7 @@ module Puma
|
|
87
87
|
@spawned += 1
|
88
88
|
|
89
89
|
th = Thread.new(@spawned) do |spawned|
|
90
|
-
|
91
|
-
Thread.current.name = 'puma %03i' % spawned if Thread.current.respond_to?(:name=)
|
90
|
+
Puma.set_thread_name 'threadpool %03i' % spawned
|
92
91
|
todo = @todo
|
93
92
|
block = @block
|
94
93
|
mutex = @mutex
|
@@ -194,6 +193,9 @@ module Puma
|
|
194
193
|
# method would not block and another request would be added into the reactor
|
195
194
|
# by the server. This would continue until a fully bufferend request
|
196
195
|
# makes it through the reactor and can then be processed by the thread pool.
|
196
|
+
#
|
197
|
+
# Returns the current number of busy threads, or +nil+ if shutting down.
|
198
|
+
#
|
197
199
|
def wait_until_not_full
|
198
200
|
@mutex.synchronize do
|
199
201
|
while true
|
@@ -203,7 +205,8 @@ module Puma
|
|
203
205
|
# is work queued that cannot be handled by waiting
|
204
206
|
# threads, then accept more work until we would
|
205
207
|
# spin up the max number of threads.
|
206
|
-
|
208
|
+
busy_threads = @spawned - @waiting + @todo.size
|
209
|
+
return busy_threads if @max > busy_threads
|
207
210
|
|
208
211
|
@not_full.wait @mutex
|
209
212
|
end
|
@@ -240,10 +243,12 @@ module Puma
|
|
240
243
|
end
|
241
244
|
end
|
242
245
|
|
243
|
-
class
|
244
|
-
def initialize(pool, timeout)
|
246
|
+
class Automaton
|
247
|
+
def initialize(pool, timeout, thread_name, message)
|
245
248
|
@pool = pool
|
246
249
|
@timeout = timeout
|
250
|
+
@thread_name = thread_name
|
251
|
+
@message = message
|
247
252
|
@running = false
|
248
253
|
end
|
249
254
|
|
@@ -251,8 +256,9 @@ module Puma
|
|
251
256
|
@running = true
|
252
257
|
|
253
258
|
@thread = Thread.new do
|
259
|
+
Puma.set_thread_name @thread_name
|
254
260
|
while @running
|
255
|
-
@pool.
|
261
|
+
@pool.public_send(@message)
|
256
262
|
sleep @timeout
|
257
263
|
end
|
258
264
|
end
|
@@ -265,36 +271,12 @@ module Puma
|
|
265
271
|
end
|
266
272
|
|
267
273
|
def auto_trim!(timeout=30)
|
268
|
-
@auto_trim =
|
274
|
+
@auto_trim = Automaton.new(self, timeout, "threadpool trimmer", :trim)
|
269
275
|
@auto_trim.start!
|
270
276
|
end
|
271
277
|
|
272
|
-
class Reaper
|
273
|
-
def initialize(pool, timeout)
|
274
|
-
@pool = pool
|
275
|
-
@timeout = timeout
|
276
|
-
@running = false
|
277
|
-
end
|
278
|
-
|
279
|
-
def start!
|
280
|
-
@running = true
|
281
|
-
|
282
|
-
@thread = Thread.new do
|
283
|
-
while @running
|
284
|
-
@pool.reap
|
285
|
-
sleep @timeout
|
286
|
-
end
|
287
|
-
end
|
288
|
-
end
|
289
|
-
|
290
|
-
def stop
|
291
|
-
@running = false
|
292
|
-
@thread.wakeup
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
278
|
def auto_reap!(timeout=5)
|
297
|
-
@reaper =
|
279
|
+
@reaper = Automaton.new(self, timeout, "threadpool reaper", :reap)
|
298
280
|
@reaper.start!
|
299
281
|
end
|
300
282
|
|
data/lib/puma/util.rb
CHANGED
@@ -1,11 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
|
3
2
|
|
4
|
-
|
5
|
-
require 'puma/rack/backports/uri/common_193'
|
6
|
-
else
|
7
|
-
require 'uri/common'
|
8
|
-
end
|
3
|
+
require 'uri/common'
|
9
4
|
|
10
5
|
module Puma
|
11
6
|
module Util
|
data/lib/rack/handler/puma.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack/handler'
|
2
4
|
|
3
5
|
module Rack
|
@@ -59,8 +61,6 @@ module Rack
|
|
59
61
|
conf
|
60
62
|
end
|
61
63
|
|
62
|
-
|
63
|
-
|
64
64
|
def self.run(app, options = {})
|
65
65
|
conf = self.config(app, options)
|
66
66
|
|
@@ -86,7 +86,7 @@ module Rack
|
|
86
86
|
"Verbose" => "Don't report each request (default: false)"
|
87
87
|
}
|
88
88
|
end
|
89
|
-
|
89
|
+
|
90
90
|
def self.set_host_port_to_config(host, port, config)
|
91
91
|
config.clear_binds! if host || port
|
92
92
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Use this Dockerfile to create minimal reproductions of issues
|
2
|
+
|
3
|
+
FROM ruby:2.6
|
4
|
+
|
5
|
+
# throw errors if Gemfile has been modified since Gemfile.lock
|
6
|
+
RUN bundle config --global frozen 1
|
7
|
+
|
8
|
+
WORKDIR /usr/src/app
|
9
|
+
|
10
|
+
COPY . .
|
11
|
+
RUN gem install bundler
|
12
|
+
RUN bundle install
|
13
|
+
RUN bundle exec rake compile
|
14
|
+
|
15
|
+
EXPOSE 9292
|
16
|
+
CMD bundle exec bin/puma test/rackup/hello.ru
|