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.

Files changed (71) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +157 -0
  3. data/README.md +155 -225
  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/http11_parser.c +84 -84
  15. data/ext/puma_http11/http11_parser.rl +9 -9
  16. data/ext/puma_http11/mini_ssl.c +51 -9
  17. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
  18. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  19. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +26 -6
  20. data/lib/puma.rb +8 -0
  21. data/lib/puma/app/status.rb +9 -0
  22. data/lib/puma/binder.rb +31 -18
  23. data/lib/puma/cli.rb +22 -7
  24. data/lib/puma/client.rb +67 -18
  25. data/lib/puma/cluster.rb +64 -19
  26. data/lib/puma/commonlogger.rb +2 -0
  27. data/lib/puma/configuration.rb +22 -14
  28. data/lib/puma/const.rb +13 -2
  29. data/lib/puma/control_cli.rb +26 -14
  30. data/lib/puma/convenient.rb +2 -0
  31. data/lib/puma/daemon_ext.rb +2 -0
  32. data/lib/puma/delegation.rb +2 -0
  33. data/lib/puma/detect.rb +2 -0
  34. data/lib/puma/dsl.rb +91 -12
  35. data/lib/puma/events.rb +3 -2
  36. data/lib/puma/io_buffer.rb +3 -6
  37. data/lib/puma/jruby_restart.rb +2 -1
  38. data/lib/puma/launcher.rb +51 -30
  39. data/lib/puma/minissl.rb +79 -28
  40. data/lib/puma/null_io.rb +2 -0
  41. data/lib/puma/plugin.rb +2 -0
  42. data/lib/puma/plugin/tmp_restart.rb +0 -1
  43. data/lib/puma/rack/builder.rb +2 -1
  44. data/lib/puma/reactor.rb +218 -30
  45. data/lib/puma/runner.rb +17 -4
  46. data/lib/puma/server.rb +113 -49
  47. data/lib/puma/single.rb +16 -5
  48. data/lib/puma/state_file.rb +2 -0
  49. data/lib/puma/tcp_logger.rb +2 -0
  50. data/lib/puma/thread_pool.rb +59 -6
  51. data/lib/puma/util.rb +2 -6
  52. data/lib/rack/handler/puma.rb +13 -2
  53. data/tools/jungle/README.md +12 -2
  54. data/tools/jungle/init.d/README.md +2 -0
  55. data/tools/jungle/init.d/puma +7 -7
  56. data/tools/jungle/init.d/run-puma +1 -1
  57. data/tools/jungle/rc.d/README.md +74 -0
  58. data/tools/jungle/rc.d/puma +61 -0
  59. data/tools/jungle/rc.d/puma.conf +10 -0
  60. data/tools/trickletest.rb +1 -1
  61. metadata +25 -87
  62. data/.github/issue_template.md +0 -20
  63. data/Gemfile +0 -12
  64. data/Manifest.txt +0 -78
  65. data/Rakefile +0 -158
  66. data/Release.md +0 -9
  67. data/gemfiles/2.1-Gemfile +0 -12
  68. data/lib/puma/compat.rb +0 -14
  69. data/lib/puma/java_io_buffer.rb +0 -45
  70. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  71. 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? or token == :none
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.exists?(File.dirname(stdout))
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.exists?(File.dirname(stderr))
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
- unless development?
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
- 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
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
- @check.close
238
- @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
239
265
 
240
- if @status != :restart and @own_binder
241
- @binder.close
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 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
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
- 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
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[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
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
- # Given the request +env+ from +client+ and a partial request body
561
- # in +body+, finish reading the body if there is one and invoke
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
- # +cl+ is the previously fetched Content-Length header if there
566
- # was one. This is an optimization to keep from having to look
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
- # Stops the acceptor thread and then causes the worker threads to finish
895
- # off the request queue before finally exiting.
896
- #
897
- def stop(sync=false)
955
+ def notify_safely(message)
898
956
  begin
899
- @notify << STOP_COMMAND
957
+ @notify << message
900
958
  rescue IOError
901
- # The server, in another thread, is shutting down
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
- begin
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
- begin
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
- %Q!{ "backlog": #{b}, "running": #{r} }!
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 false
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?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
 
3
5
  module Puma