puma 3.9.1 → 4.3.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.

Files changed (82) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +232 -0
  3. data/README.md +162 -224
  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 +38 -0
  10. data/docs/restart.md +41 -0
  11. data/docs/signals.md +56 -3
  12. data/docs/systemd.md +130 -37
  13. data/docs/tcp_mode.md +96 -0
  14. data/ext/puma_http11/PumaHttp11Service.java +2 -0
  15. data/ext/puma_http11/extconf.rb +13 -0
  16. data/ext/puma_http11/http11_parser.c +115 -140
  17. data/ext/puma_http11/http11_parser.java.rl +21 -37
  18. data/ext/puma_http11/http11_parser.rl +9 -9
  19. data/ext/puma_http11/http11_parser_common.rl +3 -3
  20. data/ext/puma_http11/mini_ssl.c +104 -8
  21. data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
  22. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +90 -108
  23. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  24. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +21 -4
  25. data/ext/puma_http11/puma_http11.c +2 -0
  26. data/lib/puma.rb +16 -0
  27. data/lib/puma/accept_nonblock.rb +7 -1
  28. data/lib/puma/app/status.rb +40 -26
  29. data/lib/puma/binder.rb +57 -74
  30. data/lib/puma/cli.rb +26 -7
  31. data/lib/puma/client.rb +243 -190
  32. data/lib/puma/cluster.rb +78 -34
  33. data/lib/puma/commonlogger.rb +2 -0
  34. data/lib/puma/configuration.rb +24 -16
  35. data/lib/puma/const.rb +36 -18
  36. data/lib/puma/control_cli.rb +46 -19
  37. data/lib/puma/detect.rb +2 -0
  38. data/lib/puma/dsl.rb +329 -68
  39. data/lib/puma/events.rb +6 -1
  40. data/lib/puma/io_buffer.rb +3 -6
  41. data/lib/puma/jruby_restart.rb +2 -1
  42. data/lib/puma/launcher.rb +120 -58
  43. data/lib/puma/minissl.rb +69 -27
  44. data/lib/puma/minissl/context_builder.rb +76 -0
  45. data/lib/puma/null_io.rb +2 -0
  46. data/lib/puma/plugin.rb +7 -2
  47. data/lib/puma/plugin/tmp_restart.rb +2 -1
  48. data/lib/puma/rack/builder.rb +4 -1
  49. data/lib/puma/rack/urlmap.rb +2 -0
  50. data/lib/puma/rack_default.rb +2 -0
  51. data/lib/puma/reactor.rb +224 -34
  52. data/lib/puma/runner.rb +25 -4
  53. data/lib/puma/server.rb +148 -62
  54. data/lib/puma/single.rb +16 -5
  55. data/lib/puma/state_file.rb +2 -0
  56. data/lib/puma/tcp_logger.rb +2 -0
  57. data/lib/puma/thread_pool.rb +61 -38
  58. data/lib/puma/util.rb +2 -6
  59. data/lib/rack/handler/puma.rb +10 -4
  60. data/tools/docker/Dockerfile +16 -0
  61. data/tools/jungle/README.md +12 -2
  62. data/tools/jungle/init.d/README.md +2 -0
  63. data/tools/jungle/init.d/puma +8 -8
  64. data/tools/jungle/init.d/run-puma +1 -1
  65. data/tools/jungle/rc.d/README.md +74 -0
  66. data/tools/jungle/rc.d/puma +61 -0
  67. data/tools/jungle/rc.d/puma.conf +10 -0
  68. data/tools/trickletest.rb +1 -2
  69. metadata +29 -56
  70. data/.github/issue_template.md +0 -20
  71. data/Gemfile +0 -14
  72. data/Manifest.txt +0 -78
  73. data/Rakefile +0 -165
  74. data/Release.md +0 -9
  75. data/gemfiles/2.1-Gemfile +0 -12
  76. data/lib/puma/compat.rb +0 -14
  77. data/lib/puma/convenient.rb +0 -23
  78. data/lib/puma/daemon_ext.rb +0 -31
  79. data/lib/puma/delegation.rb +0 -11
  80. data/lib/puma/java_io_buffer.rb +0 -45
  81. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  82. data/puma.gemspec +0 -20
@@ -1,7 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/server'
2
4
  require 'puma/const'
5
+ require 'puma/minissl/context_builder'
3
6
 
4
7
  module Puma
8
+ # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
9
+ # serve requests. This class spawns a new instance of `Puma::Server` via
10
+ # a call to `start_server`.
5
11
  class Runner
6
12
  def initialize(cli, events)
7
13
  @launcher = cli
@@ -9,6 +15,7 @@ module Puma
9
15
  @options = cli.options
10
16
  @app = nil
11
17
  @control = nil
18
+ @started_at = Time.now
12
19
  end
13
20
 
14
21
  def daemon?
@@ -19,6 +26,10 @@ module Puma
19
26
  @options[:environment] == "development"
20
27
  end
21
28
 
29
+ def test?
30
+ @options[:environment] == "test"
31
+ end
32
+
22
33
  def log(str)
23
34
  @events.log str
24
35
  end
@@ -43,17 +54,23 @@ module Puma
43
54
 
44
55
  uri = URI.parse str
45
56
 
46
- app = Puma::App::Status.new @launcher
47
-
48
57
  if token = @options[:control_auth_token]
49
- app.auth_token = token unless token.empty? or token == :none
58
+ token = nil if token.empty? || token == 'none'
50
59
  end
51
60
 
61
+ app = Puma::App::Status.new @launcher, token
62
+
52
63
  control = Puma::Server.new app, @launcher.events
53
64
  control.min_threads = 0
54
65
  control.max_threads = 1
55
66
 
56
67
  case uri.scheme
68
+ when "ssl"
69
+ log "* Starting control server on #{str}"
70
+ params = Util.parse_query uri.query
71
+ ctx = MiniSSL::ContextBuilder.new(params, @events).context
72
+
73
+ control.add_ssl_listener uri.host, uri.port, ctx
57
74
  when "tcp"
58
75
  log "* Starting control server on #{str}"
59
76
  control.add_tcp_listener uri.host, uri.port
@@ -161,7 +178,11 @@ module Puma
161
178
  server.tcp_mode!
162
179
  end
163
180
 
164
- unless development?
181
+ if @options[:early_hints]
182
+ server.early_hints = true
183
+ end
184
+
185
+ unless development? || test?
165
186
  server.leak_stack_on_error = false
166
187
  end
167
188
 
@@ -1,32 +1,38 @@
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
 
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
29
- extend Puma::Delegation
35
+ extend Forwardable
30
36
 
31
37
  attr_reader :thread
32
38
  attr_reader :events
@@ -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,16 +87,12 @@ 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
- forward :add_tcp_listener, :@binder
88
- forward :add_ssl_listener, :@binder
89
- forward :add_unix_listener, :@binder
90
- forward :connected_port, :@binder
92
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
91
93
 
92
94
  def inherit_binder(bind)
93
95
  @binder = bind
94
- @own_binder = false
95
96
  end
96
97
 
97
98
  def tcp_mode!
@@ -111,6 +112,7 @@ module Puma
111
112
  begin
112
113
  socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
113
114
  rescue IOError, SystemCallError
115
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
114
116
  end
115
117
  end
116
118
 
@@ -118,6 +120,7 @@ module Puma
118
120
  begin
119
121
  socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
120
122
  rescue IOError, SystemCallError
123
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
121
124
  end
122
125
  end
123
126
 
@@ -128,6 +131,7 @@ module Puma
128
131
  begin
129
132
  tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
130
133
  rescue IOError, SystemCallError
134
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
131
135
  @precheck_closing = false
132
136
  false
133
137
  else
@@ -156,6 +160,18 @@ module Puma
156
160
  @thread_pool and @thread_pool.spawned
157
161
  end
158
162
 
163
+
164
+ # This number represents the number of requests that
165
+ # the server is capable of taking right now.
166
+ #
167
+ # For example if the number is 5 then it means
168
+ # there are 5 threads sitting idle ready to take
169
+ # a request. If one request comes in, then the
170
+ # value would be 4 until it finishes processing.
171
+ def pool_capacity
172
+ @thread_pool and @thread_pool.pool_capacity
173
+ end
174
+
159
175
  # Lopez Mode == raw tcp apps
160
176
 
161
177
  def run_lopez_mode(background=true)
@@ -188,7 +204,10 @@ module Puma
188
204
  @events.fire :state, :running
189
205
 
190
206
  if background
191
- @thread = Thread.new { handle_servers_lopez_mode }
207
+ @thread = Thread.new do
208
+ Puma.set_thread_name "server"
209
+ handle_servers_lopez_mode
210
+ end
192
211
  return @thread
193
212
  else
194
213
  handle_servers_lopez_mode
@@ -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
 
@@ -288,11 +317,11 @@ module Puma
288
317
 
289
318
  @events.ssl_error self, addr, cert, e
290
319
  rescue HttpParserError => e
291
- client.write_400
320
+ client.write_error(400)
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
@@ -322,7 +351,10 @@ module Puma
322
351
  @events.fire :state, :running
323
352
 
324
353
  if background
325
- @thread = Thread.new { handle_servers }
354
+ @thread = Thread.new do
355
+ Puma.set_thread_name "server"
356
+ handle_servers
357
+ end
326
358
  return @thread
327
359
  else
328
360
  handle_servers
@@ -363,13 +395,20 @@ module Puma
363
395
  end
364
396
 
365
397
  pool << client
366
- 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
367
402
  end
368
403
  rescue SystemCallError
369
404
  # nothing
370
405
  rescue Errno::ECONNABORTED
371
406
  # client closed the socket even before accept
372
- io.close rescue nil
407
+ begin
408
+ io.close
409
+ rescue
410
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
411
+ end
373
412
  end
374
413
  end
375
414
  end
@@ -391,10 +430,6 @@ module Puma
391
430
  ensure
392
431
  @check.close
393
432
  @notify.close
394
-
395
- if @status != :restart and @own_binder
396
- @binder.close
397
- end
398
433
  end
399
434
 
400
435
  @events.fire :state, :done
@@ -431,6 +466,8 @@ module Puma
431
466
  clean_thread_locals = @options[:clean_thread_locals]
432
467
  close_socket = true
433
468
 
469
+ requests = 0
470
+
434
471
  while true
435
472
  case handle_request(client, buffer)
436
473
  when false
@@ -444,7 +481,19 @@ module Puma
444
481
 
445
482
  ThreadPool.clean_thread_locals if clean_thread_locals
446
483
 
447
- unless client.reset(@status == :run)
484
+ requests += 1
485
+
486
+ check_for_more_data = @status == :run
487
+
488
+ if requests >= MAX_FAST_INLINE
489
+ # This will mean that reset will only try to use the data it already
490
+ # has buffered and won't try to read more data. What this means is that
491
+ # every client, independent of their request speed, gets treated like a slow
492
+ # one once every MAX_FAST_INLINE requests.
493
+ check_for_more_data = false
494
+ end
495
+
496
+ unless client.reset(check_for_more_data)
448
497
  close_socket = false
449
498
  client.set_timeout @persistent_timeout
450
499
  @reactor.add client
@@ -473,7 +522,7 @@ module Puma
473
522
  rescue HttpParserError => e
474
523
  lowlevel_error(e, client.env)
475
524
 
476
- client.write_400
525
+ client.write_error(400)
477
526
 
478
527
  @events.parse_error self, client.env, e
479
528
 
@@ -481,7 +530,7 @@ module Puma
481
530
  rescue StandardError => e
482
531
  lowlevel_error(e, client.env)
483
532
 
484
- client.write_500
533
+ client.write_error(500)
485
534
 
486
535
  @events.unknown_error self, e, "Read"
487
536
 
@@ -491,6 +540,7 @@ module Puma
491
540
  begin
492
541
  client.close if close_socket
493
542
  rescue IOError, SystemCallError
543
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
494
544
  # Already closed
495
545
  rescue StandardError => e
496
546
  @events.unknown_error self, e, "Client"
@@ -555,19 +605,26 @@ module Puma
555
605
  end
556
606
 
557
607
  def default_server_port(env)
558
- return PORT_443 if env[HTTPS_KEY] == 'on' || env[HTTPS_KEY] == 'https'
559
- env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
608
+ 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"
609
+ PORT_443
610
+ else
611
+ PORT_80
612
+ end
560
613
  end
561
614
 
562
- # Given the request +env+ from +client+ and a partial request body
563
- # in +body+, finish reading the body if there is one and invoke
564
- # the rack app. Then construct the response and write it back to
565
- # +client+
615
+ # Takes the request +req+, invokes the Rack application to construct
616
+ # the response and writes it back to +req.io+.
566
617
  #
567
- # +cl+ is the previously fetched Content-Length header if there
568
- # was one. This is an optimization to keep from having to look
569
- # it up again.
618
+ # The second parameter +lines+ is a IO-like object unique to this thread.
619
+ # This is normally an instance of Puma::IOBuffer.
570
620
  #
621
+ # It'll return +false+ when the connection is closed, this doesn't mean
622
+ # that the response wasn't successful.
623
+ #
624
+ # It'll return +:async+ if the connection remains open but will be handled
625
+ # elsewhere, i.e. the connection has been hijacked by the Rack application.
626
+ #
627
+ # Finally, it'll return +true+ on keep-alive connections.
571
628
  def handle_request(req, lines)
572
629
  env = req.env
573
630
  client = req.io
@@ -590,7 +647,29 @@ module Puma
590
647
  head = env[REQUEST_METHOD] == HEAD
591
648
 
592
649
  env[RACK_INPUT] = body
593
- env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
650
+ env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
651
+
652
+ if @early_hints
653
+ env[EARLY_HINTS] = lambda { |headers|
654
+ begin
655
+ fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
656
+
657
+ headers.each_pair do |k, vs|
658
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
659
+ vs.to_s.split(NEWLINE).each do |v|
660
+ fast_write client, "#{k}: #{v}\r\n"
661
+ end
662
+ else
663
+ fast_write client, "#{k}: #{vs}\r\n"
664
+ end
665
+ end
666
+
667
+ fast_write client, "\r\n".freeze
668
+ rescue ConnectionError
669
+ # noop, if we lost the socket we just won't send the early hints
670
+ end
671
+ }
672
+ end
594
673
 
595
674
  # A rack extension. If the app writes #call'ables to this
596
675
  # array, we will invoke them when the request is done.
@@ -733,8 +812,8 @@ module Puma
733
812
 
734
813
  begin
735
814
  res_body.each do |part|
815
+ next if part.bytesize.zero?
736
816
  if chunked
737
- next if part.bytesize.zero?
738
817
  fast_write client, part.bytesize.to_s(16)
739
818
  fast_write client, line_ending
740
819
  fast_write client, part
@@ -884,6 +963,10 @@ module Puma
884
963
  @events.debug "Drained #{count} additional connections."
885
964
  end
886
965
 
966
+ if @status != :restart
967
+ @binder.close
968
+ end
969
+
887
970
  if @thread_pool
888
971
  if timeout = @options[:force_shutdown_after]
889
972
  @thread_pool.shutdown timeout.to_i
@@ -893,35 +976,38 @@ module Puma
893
976
  end
894
977
  end
895
978
 
896
- # Stops the acceptor thread and then causes the worker threads to finish
897
- # off the request queue before finally exiting.
898
- #
899
- def stop(sync=false)
979
+ def notify_safely(message)
900
980
  begin
901
- @notify << STOP_COMMAND
981
+ @notify << message
902
982
  rescue IOError
903
- # The server, in another thread, is shutting down
983
+ # The server, in another thread, is shutting down
984
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
985
+ rescue RuntimeError => e
986
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
987
+ if e.message.include?('IOError')
988
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
989
+ else
990
+ raise e
991
+ end
904
992
  end
993
+ end
994
+ private :notify_safely
905
995
 
996
+ # Stops the acceptor thread and then causes the worker threads to finish
997
+ # off the request queue before finally exiting.
998
+
999
+ def stop(sync=false)
1000
+ notify_safely(STOP_COMMAND)
906
1001
  @thread.join if @thread && sync
907
1002
  end
908
1003
 
909
1004
  def halt(sync=false)
910
- begin
911
- @notify << HALT_COMMAND
912
- rescue IOError
913
- # The server, in another thread, is shutting down
914
- end
915
-
1005
+ notify_safely(HALT_COMMAND)
916
1006
  @thread.join if @thread && sync
917
1007
  end
918
1008
 
919
1009
  def begin_restart
920
- begin
921
- @notify << RESTART_COMMAND
922
- rescue IOError
923
- # The server, in another thread, is shutting down
924
- end
1010
+ notify_safely(RESTART_COMMAND)
925
1011
  end
926
1012
 
927
1013
  def fast_write(io, str)