puma 5.3.1 → 5.6.4

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +152 -5
  3. data/LICENSE +0 -0
  4. data/README.md +47 -6
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +49 -16
  7. data/docs/compile_options.md +4 -2
  8. data/docs/deployment.md +53 -67
  9. data/docs/fork_worker.md +0 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +0 -0
  14. data/docs/jungle/rc.d/README.md +0 -0
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +0 -0
  17. data/docs/nginx.md +0 -0
  18. data/docs/plugins.md +15 -15
  19. data/docs/rails_dev_mode.md +2 -3
  20. data/docs/restart.md +6 -6
  21. data/docs/signals.md +11 -10
  22. data/docs/stats.md +8 -8
  23. data/docs/systemd.md +64 -67
  24. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  25. data/ext/puma_http11/ext_help.h +0 -0
  26. data/ext/puma_http11/extconf.rb +28 -5
  27. data/ext/puma_http11/http11_parser.c +23 -10
  28. data/ext/puma_http11/http11_parser.h +0 -0
  29. data/ext/puma_http11/http11_parser.java.rl +0 -0
  30. data/ext/puma_http11/http11_parser.rl +0 -0
  31. data/ext/puma_http11/http11_parser_common.rl +1 -1
  32. data/ext/puma_http11/mini_ssl.c +69 -9
  33. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  34. data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +49 -47
  36. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +28 -43
  37. data/ext/puma_http11/puma_http11.c +1 -1
  38. data/lib/puma/app/status.rb +4 -4
  39. data/lib/puma/binder.rb +51 -6
  40. data/lib/puma/cli.rb +14 -4
  41. data/lib/puma/client.rb +106 -21
  42. data/lib/puma/cluster/worker.rb +14 -17
  43. data/lib/puma/cluster/worker_handle.rb +4 -0
  44. data/lib/puma/cluster.rb +30 -24
  45. data/lib/puma/commonlogger.rb +0 -0
  46. data/lib/puma/configuration.rb +6 -1
  47. data/lib/puma/const.rb +9 -8
  48. data/lib/puma/control_cli.rb +1 -1
  49. data/lib/puma/detect.rb +8 -2
  50. data/lib/puma/dsl.rb +106 -12
  51. data/lib/puma/error_logger.rb +0 -0
  52. data/lib/puma/events.rb +0 -0
  53. data/lib/puma/io_buffer.rb +0 -0
  54. data/lib/puma/jruby_restart.rb +0 -0
  55. data/lib/puma/{json.rb → json_serialization.rb} +1 -1
  56. data/lib/puma/launcher.rb +4 -1
  57. data/lib/puma/minissl/context_builder.rb +8 -6
  58. data/lib/puma/minissl.rb +24 -23
  59. data/lib/puma/null_io.rb +0 -0
  60. data/lib/puma/plugin/tmp_restart.rb +0 -0
  61. data/lib/puma/plugin.rb +2 -2
  62. data/lib/puma/queue_close.rb +0 -0
  63. data/lib/puma/rack/builder.rb +1 -1
  64. data/lib/puma/rack/urlmap.rb +0 -0
  65. data/lib/puma/rack_default.rb +0 -0
  66. data/lib/puma/reactor.rb +0 -0
  67. data/lib/puma/request.rb +27 -11
  68. data/lib/puma/runner.rb +22 -8
  69. data/lib/puma/server.rb +45 -44
  70. data/lib/puma/single.rb +0 -0
  71. data/lib/puma/state_file.rb +41 -7
  72. data/lib/puma/systemd.rb +0 -0
  73. data/lib/puma/thread_pool.rb +7 -5
  74. data/lib/puma/util.rb +8 -1
  75. data/lib/puma.rb +2 -2
  76. data/lib/rack/handler/puma.rb +0 -0
  77. data/tools/Dockerfile +1 -1
  78. data/tools/trickletest.rb +0 -0
  79. metadata +7 -7
data/lib/puma/server.rb CHANGED
@@ -14,6 +14,7 @@ require 'puma/io_buffer'
14
14
  require 'puma/request'
15
15
 
16
16
  require 'socket'
17
+ require 'io/wait'
17
18
  require 'forwardable'
18
19
 
19
20
  module Puma
@@ -145,7 +146,7 @@ module Puma
145
146
  begin
146
147
  skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
147
148
  rescue IOError, SystemCallError
148
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
149
+ Puma::Util.purge_interrupt_queue
149
150
  end
150
151
  end
151
152
 
@@ -154,7 +155,7 @@ module Puma
154
155
  begin
155
156
  skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
156
157
  rescue IOError, SystemCallError
157
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
158
+ Puma::Util.purge_interrupt_queue
158
159
  end
159
160
  end
160
161
  else
@@ -175,7 +176,7 @@ module Puma
175
176
  begin
176
177
  tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
177
178
  rescue IOError, SystemCallError
178
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
179
+ Puma::Util.purge_interrupt_queue
179
180
  @precheck_closing = false
180
181
  false
181
182
  else
@@ -219,7 +220,7 @@ module Puma
219
220
  # up in the background to handle requests. Otherwise requests
220
221
  # are handled synchronously.
221
222
  #
222
- def run(background=true, thread_name: 'server')
223
+ def run(background=true, thread_name: 'srv')
223
224
  BasicSocket.do_not_reverse_lookup = true
224
225
 
225
226
  @events.fire :state, :booting
@@ -227,6 +228,7 @@ module Puma
227
228
  @status = :run
228
229
 
229
230
  @thread_pool = ThreadPool.new(
231
+ thread_name,
230
232
  @min_threads,
231
233
  @max_threads,
232
234
  ::Puma::IOBuffer,
@@ -313,14 +315,15 @@ module Puma
313
315
  queue_requests = @queue_requests
314
316
  drain = @options[:drain_on_shutdown] ? 0 : nil
315
317
 
316
- remote_addr_value = nil
317
- remote_addr_header = nil
318
-
319
- case @options[:remote_address]
318
+ addr_send_name, addr_value = case @options[:remote_address]
320
319
  when :value
321
- remote_addr_value = @options[:remote_address_value]
320
+ [:peerip=, @options[:remote_address_value]]
322
321
  when :header
323
- remote_addr_header = @options[:remote_address_header]
322
+ [:remote_addr_header=, @options[:remote_address_header]]
323
+ when :proxy_protocol
324
+ [:expect_proxy_proto=, @options[:remote_address_proxy_protocol]]
325
+ else
326
+ [nil, nil]
324
327
  end
325
328
 
326
329
  while @status == :run || (drain && shutting_down?)
@@ -340,16 +343,16 @@ module Puma
340
343
  next
341
344
  end
342
345
  drain += 1 if shutting_down?
343
- client = Client.new io, @binder.env(sock)
344
- if remote_addr_value
345
- client.peerip = remote_addr_value
346
- elsif remote_addr_header
347
- client.remote_addr_header = remote_addr_header
348
- end
349
- pool << client
346
+ pool << Client.new(io, @binder.env(sock)).tap { |c|
347
+ c.listener = sock
348
+ c.send(addr_send_name, addr_value) if addr_value
349
+ }
350
350
  end
351
351
  end
352
- rescue Object => e
352
+ rescue IOError, Errno::EBADF
353
+ # In the case that any of the sockets are unexpectedly close.
354
+ raise
355
+ rescue StandardError => e
353
356
  @events.unknown_error e, nil, "Listen loop"
354
357
  end
355
358
  end
@@ -365,13 +368,14 @@ module Puma
365
368
  rescue Exception => e
366
369
  @events.unknown_error e, nil, "Exception handling servers"
367
370
  ensure
368
- begin
369
- @check.close unless @check.closed?
370
- rescue Errno::EBADF, RuntimeError
371
- # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
372
- # Errno::EBADF is infrequently raised
371
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
372
+ # Errno::EBADF is infrequently raised
373
+ [@check, @notify].each do |io|
374
+ begin
375
+ io.close unless io.closed?
376
+ rescue Errno::EBADF, RuntimeError
377
+ end
373
378
  end
374
- @notify.close
375
379
  @notify = nil
376
380
  @check = nil
377
381
  end
@@ -395,7 +399,7 @@ module Puma
395
399
  return true
396
400
  end
397
401
 
398
- return false
402
+ false
399
403
  end
400
404
 
401
405
  # Given a connection on +client+, handle the incoming requests,
@@ -434,7 +438,7 @@ module Puma
434
438
 
435
439
  while true
436
440
  @requests_count += 1
437
- case handle_request(client, buffer)
441
+ case handle_request(client, buffer, requests + 1)
438
442
  when false
439
443
  break
440
444
  when :async
@@ -447,23 +451,17 @@ module Puma
447
451
 
448
452
  requests += 1
449
453
 
450
- # Closing keepalive sockets after they've made a reasonable
451
- # number of requests allows Puma to service many connections
452
- # fairly, even when the number of concurrent connections exceeds
453
- # the size of the threadpool. It also allows cluster mode Pumas
454
- # to keep load evenly distributed across workers, because clients
455
- # are randomly assigned a new worker when opening a new connection.
456
- #
457
- # Previously, Puma would kick connections in this conditional back
458
- # to the reactor. However, because this causes the todo set to increase
459
- # in size, the wait_until_full mutex would never unlock, leaving
460
- # any additional connections unserviced.
461
- break if requests >= @max_fast_inline
462
-
463
- check_for_more_data = @status == :run
454
+ # As an optimization, try to read the next request from the
455
+ # socket for a short time before returning to the reactor.
456
+ fast_check = @status == :run
457
+
458
+ # Always pass the client back to the reactor after a reasonable
459
+ # number of inline requests if there are other requests pending.
460
+ fast_check = false if requests >= @max_fast_inline &&
461
+ @thread_pool.backlog > 0
464
462
 
465
463
  next_request_ready = with_force_shutdown(client) do
466
- client.reset(check_for_more_data)
464
+ client.reset(fast_check)
467
465
  end
468
466
 
469
467
  unless next_request_ready
@@ -487,7 +485,7 @@ module Puma
487
485
  begin
488
486
  client.close if close_socket
489
487
  rescue IOError, SystemCallError
490
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
488
+ Puma::Util.purge_interrupt_queue
491
489
  # Already closed
492
490
  rescue StandardError => e
493
491
  @events.unknown_error e, nil, "Client"
@@ -517,6 +515,9 @@ module Puma
517
515
  when HttpParserError
518
516
  client.write_error(400)
519
517
  @events.parse_error e, client
518
+ when HttpParserError501
519
+ client.write_error(501)
520
+ @events.parse_error e, client
520
521
  else
521
522
  client.write_error(500)
522
523
  @events.unknown_error e, nil, "Read"
@@ -579,11 +580,11 @@ module Puma
579
580
  @notify << message
580
581
  rescue IOError, NoMethodError, Errno::EPIPE
581
582
  # The server, in another thread, is shutting down
582
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
583
+ Puma::Util.purge_interrupt_queue
583
584
  rescue RuntimeError => e
584
585
  # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
585
586
  if e.message.include?('IOError')
586
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
587
+ Puma::Util.purge_interrupt_queue
587
588
  else
588
589
  raise e
589
590
  end
data/lib/puma/single.rb CHANGED
File without changes
@@ -1,15 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yaml'
4
-
5
3
  module Puma
4
+
5
+ # Puma::Launcher uses StateFile to write a yaml file for use with Puma::ControlCLI.
6
+ #
7
+ # In previous versions of Puma, YAML was used to read/write the state file.
8
+ # Since Puma is similar to Bundler/RubyGems in that it may load before one's app
9
+ # does, minimizing the dependencies that may be shared with the app is desired.
10
+ #
11
+ # At present, it only works with numeric and string values. It is still a valid
12
+ # yaml file, and the CI tests parse it with Psych.
13
+ #
6
14
  class StateFile
15
+
16
+ ALLOWED_FIELDS = %w!control_url control_auth_token pid running_from!
17
+
18
+ # @deprecated 6.0.0
19
+ FIELDS = ALLOWED_FIELDS
20
+
7
21
  def initialize
8
22
  @options = {}
9
23
  end
10
24
 
11
25
  def save(path, permission = nil)
12
- contents =YAML.dump @options
26
+ contents = "---\n".dup
27
+ @options.each do |k,v|
28
+ next unless ALLOWED_FIELDS.include? k
29
+ case v
30
+ when Numeric
31
+ contents << "#{k}: #{v}\n"
32
+ when String
33
+ next if v.strip.empty?
34
+ contents << (k == 'running_from' || v.to_s.include?(' ') ?
35
+ "#{k}: \"#{v}\"\n" : "#{k}: #{v}\n")
36
+ end
37
+ end
13
38
  if permission
14
39
  File.write path, contents, mode: 'wb:UTF-8'
15
40
  else
@@ -18,12 +43,21 @@ module Puma
18
43
  end
19
44
 
20
45
  def load(path)
21
- @options = YAML.load File.read(path)
46
+ File.read(path).lines.each do |line|
47
+ next if line.start_with? '#'
48
+ k,v = line.split ':', 2
49
+ next unless v && ALLOWED_FIELDS.include?(k)
50
+ v = v.strip
51
+ @options[k] =
52
+ case v
53
+ when /\A\d+\z/ then v.to_i
54
+ when /\A\d+\.\d+\z/ then v.to_f
55
+ else v.gsub(/\A"|"\z/, '')
56
+ end
57
+ end
22
58
  end
23
59
 
24
- FIELDS = %w!control_url control_auth_token pid running_from!
25
-
26
- FIELDS.each do |f|
60
+ ALLOWED_FIELDS.each do |f|
27
61
  define_method f do
28
62
  @options[f]
29
63
  end
data/lib/puma/systemd.rb CHANGED
File without changes
@@ -29,7 +29,7 @@ module Puma
29
29
  # The block passed is the work that will be performed in each
30
30
  # thread.
31
31
  #
32
- def initialize(min, max, *extra, &block)
32
+ def initialize(name, min, max, *extra, &block)
33
33
  @not_empty = ConditionVariable.new
34
34
  @not_full = ConditionVariable.new
35
35
  @mutex = Mutex.new
@@ -39,6 +39,7 @@ module Puma
39
39
  @spawned = 0
40
40
  @waiting = 0
41
41
 
42
+ @name = name
42
43
  @min = Integer(min)
43
44
  @max = Integer(max)
44
45
  @block = block
@@ -71,7 +72,7 @@ module Puma
71
72
  attr_accessor :out_of_band_hook # @version 5.0.0
72
73
 
73
74
  def self.clean_thread_locals
74
- Thread.current.keys.each do |key| # rubocop: disable Performance/HashEachMethods
75
+ Thread.current.keys.each do |key| # rubocop: disable Style/HashEachMethods
75
76
  Thread.current[key] = nil unless key == :__recursive_key__
76
77
  end
77
78
  end
@@ -101,7 +102,7 @@ module Puma
101
102
  @spawned += 1
102
103
 
103
104
  th = Thread.new(@spawned) do |spawned|
104
- Puma.set_thread_name 'threadpool %03i' % spawned
105
+ Puma.set_thread_name '%s tp %03i' % [@name, spawned]
105
106
  todo = @todo
106
107
  block = @block
107
108
  mutex = @mutex
@@ -119,6 +120,7 @@ module Puma
119
120
  @trim_requested -= 1
120
121
  @spawned -= 1
121
122
  @workers.delete th
123
+ not_full.signal
122
124
  Thread.exit
123
125
  end
124
126
 
@@ -318,12 +320,12 @@ module Puma
318
320
  end
319
321
 
320
322
  def auto_trim!(timeout=30)
321
- @auto_trim = Automaton.new(self, timeout, "threadpool trimmer", :trim)
323
+ @auto_trim = Automaton.new(self, timeout, "#{@name} threadpool trimmer", :trim)
322
324
  @auto_trim.start!
323
325
  end
324
326
 
325
327
  def auto_reap!(timeout=5)
326
- @reaper = Automaton.new(self, timeout, "threadpool reaper", :reap)
328
+ @reaper = Automaton.new(self, timeout, "#{@name} threadpool reaper", :reap)
327
329
  @reaper.start!
328
330
  end
329
331
 
data/lib/puma/util.rb CHANGED
@@ -10,6 +10,13 @@ module Puma
10
10
  IO.pipe
11
11
  end
12
12
 
13
+ # An instance method on Thread has been provided to address https://bugs.ruby-lang.org/issues/13632,
14
+ # which currently effects some older versions of Ruby: 2.2.7 2.2.8 2.2.9 2.2.10 2.3.4 2.4.1
15
+ # Additional context: https://github.com/puma/puma/pull/1345
16
+ def purge_interrupt_queue
17
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
18
+ end
19
+
13
20
  # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
14
21
  # target encoding of the string returned, and it defaults to UTF-8
15
22
  if defined?(::Encoding)
@@ -61,7 +68,7 @@ module Puma
61
68
  end
62
69
  end
63
70
 
64
- return params
71
+ params
65
72
  end
66
73
 
67
74
  # A case-insensitive Hash that preserves the original case of a
data/lib/puma.rb CHANGED
@@ -12,7 +12,7 @@ require 'thread'
12
12
 
13
13
  require 'puma/puma_http11'
14
14
  require 'puma/detect'
15
- require 'puma/json'
15
+ require 'puma/json_serialization'
16
16
 
17
17
  module Puma
18
18
  autoload :Const, 'puma/const'
@@ -60,7 +60,7 @@ module Puma
60
60
 
61
61
  # @!attribute [rw] stats_object
62
62
  def self.stats
63
- Puma::JSON.generate @get_stats.stats
63
+ Puma::JSONSerialization.generate @get_stats.stats
64
64
  end
65
65
 
66
66
  # @!attribute [r] stats_hash
File without changes
data/tools/Dockerfile CHANGED
@@ -1,6 +1,6 @@
1
1
  # Use this Dockerfile to create minimal reproductions of issues
2
2
 
3
- FROM ruby:2.6
3
+ FROM ruby:3.1
4
4
 
5
5
  # throw errors if Gemfile has been modified since Gemfile.lock
6
6
  RUN bundle config --global frozen 1
data/tools/trickletest.rb CHANGED
File without changes
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.3.1
4
+ version: 5.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Phoenix
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-11 00:00:00.000000000 Z
11
+ date: 1980-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nio4r
@@ -24,9 +24,9 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.0'
27
- description: Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server
27
+ description: Puma is a simple, fast, threaded, and highly parallel HTTP 1.1 server
28
28
  for Ruby/Rack applications. Puma is intended for use in both development and production
29
- environments. It's great for highly concurrent Ruby implementations such as Rubinius
29
+ environments. It's great for highly parallel Ruby implementations such as Rubinius
30
30
  and JRuby as well as as providing process worker support to support CRuby well.
31
31
  email:
32
32
  - evan@phx.io
@@ -94,7 +94,7 @@ files:
94
94
  - lib/puma/events.rb
95
95
  - lib/puma/io_buffer.rb
96
96
  - lib/puma/jruby_restart.rb
97
- - lib/puma/json.rb
97
+ - lib/puma/json_serialization.rb
98
98
  - lib/puma/launcher.rb
99
99
  - lib/puma/minissl.rb
100
100
  - lib/puma/minissl/context_builder.rb
@@ -140,9 +140,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
140
  - !ruby/object:Gem::Version
141
141
  version: '0'
142
142
  requirements: []
143
- rubygems_version: 3.2.3
143
+ rubygems_version: 3.2.26
144
144
  signing_key:
145
145
  specification_version: 4
146
- summary: Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for
146
+ summary: Puma is a simple, fast, threaded, and highly parallel HTTP 1.1 server for
147
147
  Ruby/Rack applications
148
148
  test_files: []