puma 3.12.0 → 4.3.8
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 +164 -0
- data/README.md +76 -48
- data/docs/architecture.md +1 -0
- data/docs/deployment.md +24 -4
- data/docs/plugins.md +20 -10
- data/docs/restart.md +4 -2
- data/docs/systemd.md +27 -9
- data/docs/tcp_mode.md +96 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/extconf.rb +13 -0
- data/ext/puma_http11/http11_parser.c +40 -63
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser.rl +3 -1
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +86 -4
- data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +91 -106
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -4
- data/ext/puma_http11/puma_http11.c +3 -0
- data/lib/puma.rb +8 -0
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +37 -29
- data/lib/puma/binder.rb +47 -68
- data/lib/puma/cli.rb +6 -0
- data/lib/puma/client.rb +244 -199
- data/lib/puma/cluster.rb +55 -30
- data/lib/puma/commonlogger.rb +2 -0
- data/lib/puma/configuration.rb +6 -3
- data/lib/puma/const.rb +32 -18
- data/lib/puma/control_cli.rb +41 -14
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +311 -77
- data/lib/puma/events.rb +6 -1
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -0
- data/lib/puma/launcher.rb +99 -55
- data/lib/puma/minissl.rb +37 -17
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin.rb +7 -2
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/rack/builder.rb +4 -1
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +112 -57
- data/lib/puma/runner.rb +13 -3
- data/lib/puma/server.rb +119 -48
- data/lib/puma/single.rb +5 -3
- data/lib/puma/state_file.rb +2 -0
- data/lib/puma/tcp_logger.rb +2 -0
- data/lib/puma/thread_pool.rb +17 -33
- data/lib/puma/util.rb +2 -6
- data/lib/rack/handler/puma.rb +6 -3
- data/tools/docker/Dockerfile +16 -0
- data/tools/jungle/init.d/puma +6 -6
- data/tools/trickletest.rb +0 -1
- metadata +26 -14
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -23
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
data/lib/puma/server.rb
CHANGED
@@ -1,24 +1,21 @@
|
|
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'
|
11
|
-
require 'puma/delegation'
|
12
12
|
require 'puma/accept_nonblock'
|
13
13
|
require 'puma/util'
|
14
14
|
|
15
15
|
require 'puma/puma_http11'
|
16
16
|
|
17
|
-
unless Puma.const_defined? "IOBuffer"
|
18
|
-
require 'puma/io_buffer'
|
19
|
-
end
|
20
|
-
|
21
17
|
require 'socket'
|
18
|
+
require 'forwardable'
|
22
19
|
|
23
20
|
module Puma
|
24
21
|
|
@@ -26,7 +23,7 @@ module Puma
|
|
26
23
|
#
|
27
24
|
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
28
25
|
# to generate one or more `Puma::Server` instances capable of handling requests.
|
29
|
-
# Each Puma process will contain one `Puma::Server`
|
26
|
+
# Each Puma process will contain one `Puma::Server` instance.
|
30
27
|
#
|
31
28
|
# The `Puma::Server` instance pulls requests from the socket, adds them to a
|
32
29
|
# `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
|
@@ -35,7 +32,7 @@ module Puma
|
|
35
32
|
class Server
|
36
33
|
|
37
34
|
include Puma::Const
|
38
|
-
extend
|
35
|
+
extend Forwardable
|
39
36
|
|
40
37
|
attr_reader :thread
|
41
38
|
attr_reader :events
|
@@ -77,7 +74,6 @@ module Puma
|
|
77
74
|
@first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
|
78
75
|
|
79
76
|
@binder = Binder.new(events)
|
80
|
-
@own_binder = true
|
81
77
|
|
82
78
|
@leak_stack_on_error = true
|
83
79
|
|
@@ -93,14 +89,10 @@ module Puma
|
|
93
89
|
|
94
90
|
attr_accessor :binder, :leak_stack_on_error, :early_hints
|
95
91
|
|
96
|
-
|
97
|
-
forward :add_ssl_listener, :@binder
|
98
|
-
forward :add_unix_listener, :@binder
|
99
|
-
forward :connected_port, :@binder
|
92
|
+
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
|
100
93
|
|
101
94
|
def inherit_binder(bind)
|
102
95
|
@binder = bind
|
103
|
-
@own_binder = false
|
104
96
|
end
|
105
97
|
|
106
98
|
def tcp_mode!
|
@@ -212,7 +204,10 @@ module Puma
|
|
212
204
|
@events.fire :state, :running
|
213
205
|
|
214
206
|
if background
|
215
|
-
@thread = Thread.new
|
207
|
+
@thread = Thread.new do
|
208
|
+
Puma.set_thread_name "server"
|
209
|
+
handle_servers_lopez_mode
|
210
|
+
end
|
216
211
|
return @thread
|
217
212
|
else
|
218
213
|
handle_servers_lopez_mode
|
@@ -268,10 +263,11 @@ module Puma
|
|
268
263
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
269
264
|
end
|
270
265
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
266
|
+
# Prevent can't modify frozen IOError (RuntimeError)
|
267
|
+
begin
|
268
|
+
@notify.close
|
269
|
+
rescue IOError
|
270
|
+
# no biggy
|
275
271
|
end
|
276
272
|
end
|
277
273
|
|
@@ -321,7 +317,7 @@ module Puma
|
|
321
317
|
|
322
318
|
@events.ssl_error self, addr, cert, e
|
323
319
|
rescue HttpParserError => e
|
324
|
-
client.
|
320
|
+
client.write_error(400)
|
325
321
|
client.close
|
326
322
|
|
327
323
|
@events.parse_error self, client.env, e
|
@@ -355,7 +351,10 @@ module Puma
|
|
355
351
|
@events.fire :state, :running
|
356
352
|
|
357
353
|
if background
|
358
|
-
@thread = Thread.new
|
354
|
+
@thread = Thread.new do
|
355
|
+
Puma.set_thread_name "server"
|
356
|
+
handle_servers
|
357
|
+
end
|
359
358
|
return @thread
|
360
359
|
else
|
361
360
|
handle_servers
|
@@ -396,7 +395,10 @@ module Puma
|
|
396
395
|
end
|
397
396
|
|
398
397
|
pool << client
|
399
|
-
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
|
400
402
|
end
|
401
403
|
rescue SystemCallError
|
402
404
|
# nothing
|
@@ -428,10 +430,6 @@ module Puma
|
|
428
430
|
ensure
|
429
431
|
@check.close
|
430
432
|
@notify.close
|
431
|
-
|
432
|
-
if @status != :restart and @own_binder
|
433
|
-
@binder.close
|
434
|
-
end
|
435
433
|
end
|
436
434
|
|
437
435
|
@events.fire :state, :done
|
@@ -468,6 +466,8 @@ module Puma
|
|
468
466
|
clean_thread_locals = @options[:clean_thread_locals]
|
469
467
|
close_socket = true
|
470
468
|
|
469
|
+
requests = 0
|
470
|
+
|
471
471
|
while true
|
472
472
|
case handle_request(client, buffer)
|
473
473
|
when false
|
@@ -481,7 +481,24 @@ module Puma
|
|
481
481
|
|
482
482
|
ThreadPool.clean_thread_locals if clean_thread_locals
|
483
483
|
|
484
|
-
|
484
|
+
requests += 1
|
485
|
+
|
486
|
+
# Closing keepalive sockets after they've made a reasonable
|
487
|
+
# number of requests allows Puma to service many connections
|
488
|
+
# fairly, even when the number of concurrent connections exceeds
|
489
|
+
# the size of the threadpool. It also allows cluster mode Pumas
|
490
|
+
# to keep load evenly distributed across workers, because clients
|
491
|
+
# are randomly assigned a new worker when opening a new connection.
|
492
|
+
#
|
493
|
+
# Previously, Puma would kick connections in this conditional back
|
494
|
+
# to the reactor. However, because this causes the todo set to increase
|
495
|
+
# in size, the wait_until_full mutex would never unlock, leaving
|
496
|
+
# any additional connections unserviced.
|
497
|
+
break if requests >= MAX_FAST_INLINE
|
498
|
+
|
499
|
+
check_for_more_data = @status == :run
|
500
|
+
|
501
|
+
unless client.reset(check_for_more_data)
|
485
502
|
close_socket = false
|
486
503
|
client.set_timeout @persistent_timeout
|
487
504
|
@reactor.add client
|
@@ -510,7 +527,7 @@ module Puma
|
|
510
527
|
rescue HttpParserError => e
|
511
528
|
lowlevel_error(e, client.env)
|
512
529
|
|
513
|
-
client.
|
530
|
+
client.write_error(400)
|
514
531
|
|
515
532
|
@events.parse_error self, client.env, e
|
516
533
|
|
@@ -518,7 +535,7 @@ module Puma
|
|
518
535
|
rescue StandardError => e
|
519
536
|
lowlevel_error(e, client.env)
|
520
537
|
|
521
|
-
client.
|
538
|
+
client.write_error(500)
|
522
539
|
|
523
540
|
@events.unknown_error self, e, "Read"
|
524
541
|
|
@@ -593,19 +610,26 @@ module Puma
|
|
593
610
|
end
|
594
611
|
|
595
612
|
def default_server_port(env)
|
596
|
-
|
597
|
-
|
613
|
+
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"
|
614
|
+
PORT_443
|
615
|
+
else
|
616
|
+
PORT_80
|
617
|
+
end
|
598
618
|
end
|
599
619
|
|
600
|
-
#
|
601
|
-
#
|
602
|
-
#
|
603
|
-
# +
|
620
|
+
# Takes the request +req+, invokes the Rack application to construct
|
621
|
+
# the response and writes it back to +req.io+.
|
622
|
+
#
|
623
|
+
# The second parameter +lines+ is a IO-like object unique to this thread.
|
624
|
+
# This is normally an instance of Puma::IOBuffer.
|
625
|
+
#
|
626
|
+
# It'll return +false+ when the connection is closed, this doesn't mean
|
627
|
+
# that the response wasn't successful.
|
604
628
|
#
|
605
|
-
# +
|
606
|
-
#
|
607
|
-
# it up again.
|
629
|
+
# It'll return +:async+ if the connection remains open but will be handled
|
630
|
+
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
608
631
|
#
|
632
|
+
# Finally, it'll return +true+ on keep-alive connections.
|
609
633
|
def handle_request(req, lines)
|
610
634
|
env = req.env
|
611
635
|
client = req.io
|
@@ -628,26 +652,62 @@ module Puma
|
|
628
652
|
head = env[REQUEST_METHOD] == HEAD
|
629
653
|
|
630
654
|
env[RACK_INPUT] = body
|
631
|
-
env[RACK_URL_SCHEME] =
|
655
|
+
env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
632
656
|
|
633
657
|
if @early_hints
|
634
658
|
env[EARLY_HINTS] = lambda { |headers|
|
635
|
-
|
659
|
+
begin
|
660
|
+
fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
|
636
661
|
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
662
|
+
headers.each_pair do |k, vs|
|
663
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
664
|
+
vs.to_s.split(NEWLINE).each do |v|
|
665
|
+
next if possible_header_injection?(v)
|
666
|
+
fast_write client, "#{k}: #{v}\r\n"
|
667
|
+
end
|
668
|
+
else
|
669
|
+
fast_write client, "#{k}: #{vs}\r\n"
|
641
670
|
end
|
642
|
-
else
|
643
|
-
fast_write client, "#{k}: #{vs}\r\n"
|
644
671
|
end
|
645
|
-
end
|
646
672
|
|
647
|
-
|
673
|
+
fast_write client, "\r\n".freeze
|
674
|
+
rescue ConnectionError
|
675
|
+
# noop, if we lost the socket we just won't send the early hints
|
676
|
+
end
|
648
677
|
}
|
649
678
|
end
|
650
679
|
|
680
|
+
# Fixup any headers with , in the name to have _ now. We emit
|
681
|
+
# headers with , in them during the parse phase to avoid ambiguity
|
682
|
+
# with the - to _ conversion for critical headers. But here for
|
683
|
+
# compatibility, we'll convert them back. This code is written to
|
684
|
+
# avoid allocation in the common case (ie there are no headers
|
685
|
+
# with , in their names), that's why it has the extra conditionals.
|
686
|
+
|
687
|
+
to_delete = nil
|
688
|
+
to_add = nil
|
689
|
+
|
690
|
+
env.each do |k,v|
|
691
|
+
if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
|
692
|
+
if to_delete
|
693
|
+
to_delete << k
|
694
|
+
else
|
695
|
+
to_delete = [k]
|
696
|
+
end
|
697
|
+
|
698
|
+
unless to_add
|
699
|
+
to_add = {}
|
700
|
+
end
|
701
|
+
|
702
|
+
to_add[k.tr(",", "_")] = v
|
703
|
+
end
|
704
|
+
end
|
705
|
+
|
706
|
+
if to_delete
|
707
|
+
to_delete.each { |k| env.delete(k) }
|
708
|
+
env.merge! to_add
|
709
|
+
end
|
710
|
+
|
651
711
|
# A rack extension. If the app writes #call'ables to this
|
652
712
|
# array, we will invoke them when the request is done.
|
653
713
|
#
|
@@ -735,6 +795,7 @@ module Puma
|
|
735
795
|
headers.each do |k, vs|
|
736
796
|
case k.downcase
|
737
797
|
when CONTENT_LENGTH2
|
798
|
+
next if possible_header_injection?(vs)
|
738
799
|
content_length = vs
|
739
800
|
next
|
740
801
|
when TRANSFER_ENCODING
|
@@ -747,6 +808,7 @@ module Puma
|
|
747
808
|
|
748
809
|
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
749
810
|
vs.to_s.split(NEWLINE).each do |v|
|
811
|
+
next if possible_header_injection?(v)
|
750
812
|
lines.append k, colon, v, line_ending
|
751
813
|
end
|
752
814
|
else
|
@@ -940,6 +1002,10 @@ module Puma
|
|
940
1002
|
@events.debug "Drained #{count} additional connections."
|
941
1003
|
end
|
942
1004
|
|
1005
|
+
if @status != :restart
|
1006
|
+
@binder.close
|
1007
|
+
end
|
1008
|
+
|
943
1009
|
if @thread_pool
|
944
1010
|
if timeout = @options[:force_shutdown_after]
|
945
1011
|
@thread_pool.shutdown timeout.to_i
|
@@ -1013,5 +1079,10 @@ module Puma
|
|
1013
1079
|
def shutting_down?
|
1014
1080
|
@status == :stop || @status == :restart
|
1015
1081
|
end
|
1082
|
+
|
1083
|
+
def possible_header_injection?(header_value)
|
1084
|
+
HTTP_INJECTION_REGEX =~ header_value.to_s
|
1085
|
+
end
|
1086
|
+
private :possible_header_injection?
|
1016
1087
|
end
|
1017
1088
|
end
|
data/lib/puma/single.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/runner'
|
2
4
|
require 'puma/detect'
|
3
5
|
require 'puma/plugin'
|
@@ -16,7 +18,7 @@ module Puma
|
|
16
18
|
r = @server.running || 0
|
17
19
|
t = @server.pool_capacity || 0
|
18
20
|
m = @server.max_threads || 0
|
19
|
-
%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} }!
|
20
22
|
end
|
21
23
|
|
22
24
|
def restart
|
@@ -24,7 +26,7 @@ module Puma
|
|
24
26
|
end
|
25
27
|
|
26
28
|
def stop
|
27
|
-
@server.stop
|
29
|
+
@server.stop(false) if @server
|
28
30
|
end
|
29
31
|
|
30
32
|
def halt
|
@@ -34,7 +36,7 @@ module Puma
|
|
34
36
|
def stop_blocked
|
35
37
|
log "- Gracefully stopping, waiting for requests to finish"
|
36
38
|
@control.stop(true) if @control
|
37
|
-
@server.stop(true)
|
39
|
+
@server.stop(true) if @server
|
38
40
|
end
|
39
41
|
|
40
42
|
def jruby_daemon?
|
data/lib/puma/state_file.rb
CHANGED
data/lib/puma/tcp_logger.rb
CHANGED
data/lib/puma/thread_pool.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'thread'
|
2
4
|
|
3
5
|
module Puma
|
@@ -85,8 +87,7 @@ module Puma
|
|
85
87
|
@spawned += 1
|
86
88
|
|
87
89
|
th = Thread.new(@spawned) do |spawned|
|
88
|
-
|
89
|
-
Thread.current.name = 'puma %03i' % spawned if Thread.current.respond_to?(:name=)
|
90
|
+
Puma.set_thread_name 'threadpool %03i' % spawned
|
90
91
|
todo = @todo
|
91
92
|
block = @block
|
92
93
|
mutex = @mutex
|
@@ -188,10 +189,13 @@ module Puma
|
|
188
189
|
# request, it might not be added to the `@todo` array right away.
|
189
190
|
# For example if a slow client has only sent a header, but not a body
|
190
191
|
# then the `@todo` array would stay the same size as the reactor works
|
191
|
-
# to try to buffer the request. In
|
192
|
+
# to try to buffer the request. In that scenario the next call to this
|
192
193
|
# method would not block and another request would be added into the reactor
|
193
194
|
# by the server. This would continue until a fully bufferend request
|
194
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
|
+
#
|
195
199
|
def wait_until_not_full
|
196
200
|
@mutex.synchronize do
|
197
201
|
while true
|
@@ -201,7 +205,8 @@ module Puma
|
|
201
205
|
# is work queued that cannot be handled by waiting
|
202
206
|
# threads, then accept more work until we would
|
203
207
|
# spin up the max number of threads.
|
204
|
-
|
208
|
+
busy_threads = @spawned - @waiting + @todo.size
|
209
|
+
return busy_threads if @max > busy_threads
|
205
210
|
|
206
211
|
@not_full.wait @mutex
|
207
212
|
end
|
@@ -238,10 +243,12 @@ module Puma
|
|
238
243
|
end
|
239
244
|
end
|
240
245
|
|
241
|
-
class
|
242
|
-
def initialize(pool, timeout)
|
246
|
+
class Automaton
|
247
|
+
def initialize(pool, timeout, thread_name, message)
|
243
248
|
@pool = pool
|
244
249
|
@timeout = timeout
|
250
|
+
@thread_name = thread_name
|
251
|
+
@message = message
|
245
252
|
@running = false
|
246
253
|
end
|
247
254
|
|
@@ -249,8 +256,9 @@ module Puma
|
|
249
256
|
@running = true
|
250
257
|
|
251
258
|
@thread = Thread.new do
|
259
|
+
Puma.set_thread_name @thread_name
|
252
260
|
while @running
|
253
|
-
@pool.
|
261
|
+
@pool.public_send(@message)
|
254
262
|
sleep @timeout
|
255
263
|
end
|
256
264
|
end
|
@@ -263,36 +271,12 @@ module Puma
|
|
263
271
|
end
|
264
272
|
|
265
273
|
def auto_trim!(timeout=30)
|
266
|
-
@auto_trim =
|
274
|
+
@auto_trim = Automaton.new(self, timeout, "threadpool trimmer", :trim)
|
267
275
|
@auto_trim.start!
|
268
276
|
end
|
269
277
|
|
270
|
-
class Reaper
|
271
|
-
def initialize(pool, timeout)
|
272
|
-
@pool = pool
|
273
|
-
@timeout = timeout
|
274
|
-
@running = false
|
275
|
-
end
|
276
|
-
|
277
|
-
def start!
|
278
|
-
@running = true
|
279
|
-
|
280
|
-
@thread = Thread.new do
|
281
|
-
while @running
|
282
|
-
@pool.reap
|
283
|
-
sleep @timeout
|
284
|
-
end
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
def stop
|
289
|
-
@running = false
|
290
|
-
@thread.wakeup
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
278
|
def auto_reap!(timeout=5)
|
295
|
-
@reaper =
|
279
|
+
@reaper = Automaton.new(self, timeout, "threadpool reaper", :reap)
|
296
280
|
@reaper.start!
|
297
281
|
end
|
298
282
|
|