puma 3.7.1 → 4.1.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.

Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +229 -1
  3. data/README.md +179 -212
  4. data/docs/architecture.md +37 -0
  5. data/{DEPLOYMENT.md → docs/deployment.md} +24 -4
  6. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  7. data/docs/images/puma-connection-flow.png +0 -0
  8. data/docs/images/puma-general-arch.png +0 -0
  9. data/docs/plugins.md +28 -0
  10. data/docs/restart.md +41 -0
  11. data/docs/signals.md +56 -3
  12. data/docs/systemd.md +130 -37
  13. data/ext/puma_http11/PumaHttp11Service.java +2 -0
  14. data/ext/puma_http11/extconf.rb +8 -0
  15. data/ext/puma_http11/http11_parser.c +84 -84
  16. data/ext/puma_http11/http11_parser.rl +9 -9
  17. data/ext/puma_http11/mini_ssl.c +105 -9
  18. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
  19. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  20. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
  21. data/lib/puma.rb +10 -0
  22. data/lib/puma/accept_nonblock.rb +2 -0
  23. data/lib/puma/app/status.rb +13 -0
  24. data/lib/puma/binder.rb +33 -18
  25. data/lib/puma/cli.rb +48 -33
  26. data/lib/puma/client.rb +94 -22
  27. data/lib/puma/cluster.rb +69 -21
  28. data/lib/puma/commonlogger.rb +2 -0
  29. data/lib/puma/configuration.rb +134 -136
  30. data/lib/puma/const.rb +16 -2
  31. data/lib/puma/control_cli.rb +31 -18
  32. data/lib/puma/convenient.rb +5 -3
  33. data/lib/puma/daemon_ext.rb +2 -0
  34. data/lib/puma/delegation.rb +2 -0
  35. data/lib/puma/detect.rb +2 -0
  36. data/lib/puma/dsl.rb +349 -113
  37. data/lib/puma/events.rb +8 -4
  38. data/lib/puma/io_buffer.rb +3 -6
  39. data/lib/puma/jruby_restart.rb +2 -1
  40. data/lib/puma/launcher.rb +60 -36
  41. data/lib/puma/minissl.rb +85 -28
  42. data/lib/puma/null_io.rb +2 -0
  43. data/lib/puma/plugin.rb +2 -0
  44. data/lib/puma/plugin/tmp_restart.rb +3 -2
  45. data/lib/puma/rack/builder.rb +4 -1
  46. data/lib/puma/rack/urlmap.rb +2 -0
  47. data/lib/puma/rack_default.rb +2 -0
  48. data/lib/puma/reactor.rb +218 -30
  49. data/lib/puma/runner.rb +18 -4
  50. data/lib/puma/server.rb +149 -56
  51. data/lib/puma/single.rb +16 -5
  52. data/lib/puma/state_file.rb +2 -0
  53. data/lib/puma/tcp_logger.rb +2 -0
  54. data/lib/puma/thread_pool.rb +59 -6
  55. data/lib/puma/util.rb +2 -6
  56. data/lib/rack/handler/puma.rb +58 -19
  57. data/tools/jungle/README.md +12 -2
  58. data/tools/jungle/init.d/README.md +2 -0
  59. data/tools/jungle/init.d/puma +8 -8
  60. data/tools/jungle/init.d/run-puma +1 -1
  61. data/tools/jungle/rc.d/README.md +74 -0
  62. data/tools/jungle/rc.d/puma +61 -0
  63. data/tools/jungle/rc.d/puma.conf +10 -0
  64. data/tools/trickletest.rb +1 -1
  65. metadata +25 -85
  66. data/.github/issue_template.md +0 -20
  67. data/Gemfile +0 -12
  68. data/Manifest.txt +0 -77
  69. data/Rakefile +0 -158
  70. data/gemfiles/2.1-Gemfile +0 -12
  71. data/lib/puma/compat.rb +0 -14
  72. data/lib/puma/java_io_buffer.rb +0 -45
  73. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  74. 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
@@ -9,6 +14,7 @@ module Puma
9
14
  @options = cli.options
10
15
  @app = nil
11
16
  @control = nil
17
+ @started_at = Time.now
12
18
  end
13
19
 
14
20
  def daemon?
@@ -19,6 +25,10 @@ module Puma
19
25
  @options[:environment] == "development"
20
26
  end
21
27
 
28
+ def test?
29
+ @options[:environment] == "test"
30
+ end
31
+
22
32
  def log(str)
23
33
  @events.log str
24
34
  end
@@ -46,7 +56,7 @@ module Puma
46
56
  app = Puma::App::Status.new @launcher
47
57
 
48
58
  if token = @options[:control_auth_token]
49
- app.auth_token = token unless token.empty? or token == :none
59
+ app.auth_token = token unless token.empty? || token == 'none'
50
60
  end
51
61
 
52
62
  control = Puma::Server.new app, @launcher.events
@@ -107,7 +117,7 @@ module Puma
107
117
  append = @options[:redirect_append]
108
118
 
109
119
  if stdout
110
- unless Dir.exists?(File.dirname(stdout))
120
+ unless Dir.exist?(File.dirname(stdout))
111
121
  raise "Cannot redirect STDOUT to #{stdout}"
112
122
  end
113
123
 
@@ -117,7 +127,7 @@ module Puma
117
127
  end
118
128
 
119
129
  if stderr
120
- unless Dir.exists?(File.dirname(stderr))
130
+ unless Dir.exist?(File.dirname(stderr))
121
131
  raise "Cannot redirect STDERR to #{stderr}"
122
132
  end
123
133
 
@@ -161,7 +171,11 @@ module Puma
161
171
  server.tcp_mode!
162
172
  end
163
173
 
164
- unless development?
174
+ if @options[:early_hints]
175
+ server.early_hints = true
176
+ end
177
+
178
+ unless development? || test?
165
179
  server.leak_stack_on_error = false
166
180
  end
167
181
 
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
 
@@ -78,9 +83,11 @@ module Puma
78
83
  ENV['RACK_ENV'] ||= "development"
79
84
 
80
85
  @mode = :http
86
+
87
+ @precheck_closing = true
81
88
  end
82
89
 
83
- attr_accessor :binder, :leak_stack_on_error
90
+ attr_accessor :binder, :leak_stack_on_error, :early_hints
84
91
 
85
92
  forward :add_tcp_listener, :@binder
86
93
  forward :add_ssl_listener, :@binder
@@ -89,7 +96,6 @@ module Puma
89
96
 
90
97
  def inherit_binder(bind)
91
98
  @binder = bind
92
- @own_binder = false
93
99
  end
94
100
 
95
101
  def tcp_mode!
@@ -100,6 +106,8 @@ module Puma
100
106
  # packetizes our stream. This improves both latency and throughput.
101
107
  #
102
108
  if RUBY_PLATFORM =~ /linux/
109
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
110
+
103
111
  # 6 == Socket::IPPROTO_TCP
104
112
  # 3 == TCP_CORK
105
113
  # 1/0 == turn on/off
@@ -107,6 +115,7 @@ module Puma
107
115
  begin
108
116
  socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
109
117
  rescue IOError, SystemCallError
118
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
110
119
  end
111
120
  end
112
121
 
@@ -114,6 +123,24 @@ module Puma
114
123
  begin
115
124
  socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
116
125
  rescue IOError, SystemCallError
126
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
127
+ end
128
+ end
129
+
130
+ def closed_socket?(socket)
131
+ return false unless socket.kind_of? TCPSocket
132
+ return false unless @precheck_closing
133
+
134
+ begin
135
+ tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
136
+ rescue IOError, SystemCallError
137
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
138
+ @precheck_closing = false
139
+ false
140
+ else
141
+ state = tcp_info.unpack(UNPACK_TCP_STATE_FROM_TCP_INFO)[0]
142
+ # TIME_WAIT: 6, CLOSE: 7, CLOSE_WAIT: 8, LAST_ACK: 9, CLOSING: 11
143
+ (state >= 6 && state <= 9) || state == 11
117
144
  end
118
145
  end
119
146
  else
@@ -122,6 +149,10 @@ module Puma
122
149
 
123
150
  def uncork_socket(socket)
124
151
  end
152
+
153
+ def closed_socket?(socket)
154
+ false
155
+ end
125
156
  end
126
157
 
127
158
  def backlog
@@ -132,6 +163,18 @@ module Puma
132
163
  @thread_pool and @thread_pool.spawned
133
164
  end
134
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
+
135
178
  # Lopez Mode == raw tcp apps
136
179
 
137
180
  def run_lopez_mode(background=true)
@@ -193,7 +236,11 @@ module Puma
193
236
  # nothing
194
237
  rescue Errno::ECONNABORTED
195
238
  # client closed the socket even before accept
196
- io.close rescue nil
239
+ begin
240
+ io.close
241
+ rescue
242
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
243
+ end
197
244
  end
198
245
  end
199
246
  end
@@ -210,11 +257,17 @@ module Puma
210
257
  STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
211
258
  STDERR.puts e.backtrace
212
259
  ensure
213
- @check.close
214
- @notify.close
260
+ begin
261
+ @check.close
262
+ rescue
263
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
264
+ end
215
265
 
216
- if @status != :restart and @own_binder
217
- @binder.close
266
+ # Prevent can't modify frozen IOError (RuntimeError)
267
+ begin
268
+ @notify.close
269
+ rescue IOError
270
+ # no biggy
218
271
  end
219
272
  end
220
273
 
@@ -268,7 +321,7 @@ module Puma
268
321
  client.close
269
322
 
270
323
  @events.parse_error self, client.env, e
271
- rescue ConnectionError
324
+ rescue ConnectionError, EOFError
272
325
  client.close
273
326
  else
274
327
  if process_now
@@ -339,13 +392,20 @@ module Puma
339
392
  end
340
393
 
341
394
  pool << client
342
- pool.wait_until_not_full unless queue_requests
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
343
399
  end
344
400
  rescue SystemCallError
345
401
  # nothing
346
402
  rescue Errno::ECONNABORTED
347
403
  # client closed the socket even before accept
348
- io.close rescue nil
404
+ begin
405
+ io.close
406
+ rescue
407
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
408
+ end
349
409
  end
350
410
  end
351
411
  end
@@ -367,10 +427,6 @@ module Puma
367
427
  ensure
368
428
  @check.close
369
429
  @notify.close
370
-
371
- if @status != :restart and @own_binder
372
- @binder.close
373
- end
374
430
  end
375
431
 
376
432
  @events.fire :state, :done
@@ -404,10 +460,6 @@ module Puma
404
460
  def process_client(client, buffer)
405
461
  begin
406
462
 
407
- if client.env[HTTP_EXPECT] == CONTINUE
408
- client.io << HTTP_11_100
409
- end
410
-
411
463
  clean_thread_locals = @options[:clean_thread_locals]
412
464
  close_socket = true
413
465
 
@@ -471,6 +523,7 @@ module Puma
471
523
  begin
472
524
  client.close if close_socket
473
525
  rescue IOError, SystemCallError
526
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
474
527
  # Already closed
475
528
  rescue StandardError => e
476
529
  @events.unknown_error self, e, "Client"
@@ -502,7 +555,9 @@ module Puma
502
555
 
503
556
  raise "No REQUEST PATH" unless env[REQUEST_PATH]
504
557
 
505
- env[QUERY_STRING] = uri.query
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
506
561
  end
507
562
 
508
563
  env[PATH_INFO] = env[REQUEST_PATH]
@@ -533,23 +588,32 @@ module Puma
533
588
  end
534
589
 
535
590
  def default_server_port(env)
536
- return PORT_443 if env[HTTPS_KEY] == 'on' || env[HTTPS_KEY] == 'https'
537
- env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
591
+ 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"
592
+ PORT_443
593
+ else
594
+ PORT_80
595
+ end
538
596
  end
539
597
 
540
- # Given the request +env+ from +client+ and a partial request body
541
- # in +body+, finish reading the body if there is one and invoke
542
- # the rack app. Then construct the response and write it back to
543
- # +client+
598
+ # Takes the request +req+, invokes the Rack application to construct
599
+ # the response and writes it back to +req.io+.
544
600
  #
545
- # +cl+ is the previously fetched Content-Length header if there
546
- # was one. This is an optimization to keep from having to look
547
- # it up again.
601
+ # The second parameter +lines+ is a IO-like object unique to this thread.
602
+ # This is normally an instance of Puma::IOBuffer.
548
603
  #
604
+ # It'll return +false+ when the connection is closed, this doesn't mean
605
+ # that the response wasn't successful.
606
+ #
607
+ # It'll return +:async+ if the connection remains open but will be handled
608
+ # elsewhere, i.e. the connection has been hijacked by the Rack application.
609
+ #
610
+ # Finally, it'll return +true+ on keep-alive connections.
549
611
  def handle_request(req, lines)
550
612
  env = req.env
551
613
  client = req.io
552
614
 
615
+ return false if closed_socket?(client)
616
+
553
617
  normalize_env env, req
554
618
 
555
619
  env[PUMA_SOCKET] = client
@@ -566,7 +630,29 @@ module Puma
566
630
  head = env[REQUEST_METHOD] == HEAD
567
631
 
568
632
  env[RACK_INPUT] = body
569
- env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
633
+ env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
634
+
635
+ if @early_hints
636
+ env[EARLY_HINTS] = lambda { |headers|
637
+ begin
638
+ fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
639
+
640
+ headers.each_pair do |k, vs|
641
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
642
+ vs.to_s.split(NEWLINE).each do |v|
643
+ fast_write client, "#{k}: #{v}\r\n"
644
+ end
645
+ else
646
+ fast_write client, "#{k}: #{vs}\r\n"
647
+ end
648
+ end
649
+
650
+ fast_write client, "\r\n".freeze
651
+ rescue ConnectionError
652
+ # noop, if we lost the socket we just won't send the early hints
653
+ end
654
+ }
655
+ end
570
656
 
571
657
  # A rack extension. If the app writes #call'ables to this
572
658
  # array, we will invoke them when the request is done.
@@ -665,7 +751,7 @@ module Puma
665
751
  next
666
752
  end
667
753
 
668
- if vs.respond_to?(:to_s)
754
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
669
755
  vs.to_s.split(NEWLINE).each do |v|
670
756
  lines.append k, colon, v, line_ending
671
757
  end
@@ -709,8 +795,8 @@ module Puma
709
795
 
710
796
  begin
711
797
  res_body.each do |part|
798
+ next if part.bytesize.zero?
712
799
  if chunked
713
- next if part.bytesize.zero?
714
800
  fast_write client, part.bytesize.to_s(16)
715
801
  fast_write client, line_ending
716
802
  fast_write client, part
@@ -860,6 +946,10 @@ module Puma
860
946
  @events.debug "Drained #{count} additional connections."
861
947
  end
862
948
 
949
+ if @status != :restart
950
+ @binder.close
951
+ end
952
+
863
953
  if @thread_pool
864
954
  if timeout = @options[:force_shutdown_after]
865
955
  @thread_pool.shutdown timeout.to_i
@@ -869,35 +959,38 @@ module Puma
869
959
  end
870
960
  end
871
961
 
872
- # Stops the acceptor thread and then causes the worker threads to finish
873
- # off the request queue before finally exiting.
874
- #
875
- def stop(sync=false)
962
+ def notify_safely(message)
876
963
  begin
877
- @notify << STOP_COMMAND
964
+ @notify << message
878
965
  rescue IOError
879
- # The server, in another thread, is shutting down
966
+ # The server, in another thread, is shutting down
967
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
968
+ rescue RuntimeError => e
969
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
970
+ if e.message.include?('IOError')
971
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
972
+ else
973
+ raise e
974
+ end
880
975
  end
976
+ end
977
+ private :notify_safely
978
+
979
+ # Stops the acceptor thread and then causes the worker threads to finish
980
+ # off the request queue before finally exiting.
881
981
 
982
+ def stop(sync=false)
983
+ notify_safely(STOP_COMMAND)
882
984
  @thread.join if @thread && sync
883
985
  end
884
986
 
885
987
  def halt(sync=false)
886
- begin
887
- @notify << HALT_COMMAND
888
- rescue IOError
889
- # The server, in another thread, is shutting down
890
- end
891
-
988
+ notify_safely(HALT_COMMAND)
892
989
  @thread.join if @thread && sync
893
990
  end
894
991
 
895
992
  def begin_restart
896
- begin
897
- @notify << RESTART_COMMAND
898
- rescue IOError
899
- # The server, in another thread, is shutting down
900
- end
993
+ notify_safely(RESTART_COMMAND)
901
994
  end
902
995
 
903
996
  def fast_write(io, str)