puma 3.11.4 → 6.0.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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1717 -432
  3. data/LICENSE +23 -20
  4. data/README.md +190 -64
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +59 -21
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +69 -58
  9. data/docs/fork_worker.md +31 -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 +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +2 -2
  19. data/docs/plugins.md +22 -12
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +47 -22
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +95 -120
  25. data/docs/testing_benchmarks_local_files.md +150 -0
  26. data/docs/testing_test_rackup_ci_files.md +36 -0
  27. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  28. data/ext/puma_http11/ext_help.h +1 -1
  29. data/ext/puma_http11/extconf.rb +61 -3
  30. data/ext/puma_http11/http11_parser.c +106 -118
  31. data/ext/puma_http11/http11_parser.h +2 -2
  32. data/ext/puma_http11/http11_parser.java.rl +22 -38
  33. data/ext/puma_http11/http11_parser.rl +6 -4
  34. data/ext/puma_http11/http11_parser_common.rl +6 -6
  35. data/ext/puma_http11/mini_ssl.c +376 -93
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +250 -88
  40. data/ext/puma_http11/puma_http11.c +49 -57
  41. data/lib/puma/app/status.rb +71 -49
  42. data/lib/puma/binder.rb +243 -148
  43. data/lib/puma/cli.rb +50 -36
  44. data/lib/puma/client.rb +373 -233
  45. data/lib/puma/cluster/worker.rb +175 -0
  46. data/lib/puma/cluster/worker_handle.rb +97 -0
  47. data/lib/puma/cluster.rb +268 -235
  48. data/lib/puma/commonlogger.rb +4 -2
  49. data/lib/puma/configuration.rb +116 -88
  50. data/lib/puma/const.rb +49 -30
  51. data/lib/puma/control_cli.rb +123 -76
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +685 -135
  54. data/lib/puma/error_logger.rb +112 -0
  55. data/lib/puma/events.rb +17 -111
  56. data/lib/puma/io_buffer.rb +44 -5
  57. data/lib/puma/jruby_restart.rb +4 -59
  58. data/lib/puma/json_serialization.rb +96 -0
  59. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  60. data/lib/puma/launcher.rb +196 -130
  61. data/lib/puma/log_writer.rb +137 -0
  62. data/lib/puma/minissl/context_builder.rb +92 -0
  63. data/lib/puma/minissl.rb +249 -69
  64. data/lib/puma/null_io.rb +20 -1
  65. data/lib/puma/plugin/tmp_restart.rb +3 -1
  66. data/lib/puma/plugin.rb +9 -13
  67. data/lib/puma/rack/builder.rb +8 -9
  68. data/lib/puma/rack/urlmap.rb +2 -0
  69. data/lib/puma/rack_default.rb +3 -1
  70. data/lib/puma/reactor.rb +90 -187
  71. data/lib/puma/request.rb +644 -0
  72. data/lib/puma/runner.rb +94 -71
  73. data/lib/puma/server.rb +337 -715
  74. data/lib/puma/single.rb +27 -72
  75. data/lib/puma/state_file.rb +46 -7
  76. data/lib/puma/systemd.rb +47 -0
  77. data/lib/puma/thread_pool.rb +184 -93
  78. data/lib/puma/util.rb +23 -10
  79. data/lib/puma.rb +60 -3
  80. data/lib/rack/handler/puma.rb +17 -15
  81. data/tools/Dockerfile +16 -0
  82. data/tools/trickletest.rb +0 -1
  83. metadata +53 -33
  84. data/ext/puma_http11/io_buffer.c +0 -155
  85. data/lib/puma/accept_nonblock.rb +0 -23
  86. data/lib/puma/compat.rb +0 -14
  87. data/lib/puma/convenient.rb +0 -23
  88. data/lib/puma/daemon_ext.rb +0 -31
  89. data/lib/puma/delegation.rb +0 -11
  90. data/lib/puma/java_io_buffer.rb +0 -45
  91. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  92. data/lib/puma/tcp_logger.rb +0 -39
  93. data/tools/jungle/README.md +0 -19
  94. data/tools/jungle/init.d/README.md +0 -61
  95. data/tools/jungle/init.d/puma +0 -421
  96. data/tools/jungle/init.d/run-puma +0 -18
  97. data/tools/jungle/upstart/README.md +0 -61
  98. data/tools/jungle/upstart/puma-manager.conf +0 -31
  99. data/tools/jungle/upstart/puma.conf +0 -69
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  end
3
5
 
@@ -65,10 +67,6 @@ module Puma::Rack
65
67
  options[:environment] = e
66
68
  }
67
69
 
68
- opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
69
- options[:daemonize] = d ? true : false
70
- }
71
-
72
70
  opts.on("-P", "--pid FILE", "file to store PID") { |f|
73
71
  options[:pid] = ::File.expand_path(f)
74
72
  }
@@ -104,13 +102,14 @@ module Puma::Rack
104
102
  begin
105
103
  info = []
106
104
  server = Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
107
- if server && server.respond_to?(:valid_options)
105
+ if server&.respond_to?(:valid_options)
108
106
  info << ""
109
107
  info << "Server-specific options for #{server.name}:"
110
108
 
111
109
  has_options = false
112
110
  server.valid_options.each do |name, description|
113
- next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
111
+ next if /^(Host|Port)[^a-zA-Z]/.match? name.to_s # ignore handler's host and port options, we do our own.
112
+
114
113
  info << " -O %-21s %s" % [name, description]
115
114
  has_options = true
116
115
  end
@@ -166,7 +165,7 @@ module Puma::Rack
166
165
  require config
167
166
  app = Object.const_get(::File.basename(config, '.rb').capitalize)
168
167
  end
169
- return app, options
168
+ [app, options]
170
169
  end
171
170
 
172
171
  def self.new_from_string(builder_script, file="(rackup)")
@@ -277,7 +276,7 @@ module Puma::Rack
277
276
  app = @map ? generate_map(@run, @map) : @run
278
277
  fail "missing run or map statement" unless app
279
278
  app = @use.reverse.inject(app) { |a,e| e[a] }
280
- @warmup.call(app) if @warmup
279
+ @warmup&.call app
281
280
  app
282
281
  end
283
282
 
@@ -288,7 +287,7 @@ module Puma::Rack
288
287
  private
289
288
 
290
289
  def generate_map(default_app, mapping)
291
- require 'puma/rack/urlmap'
290
+ require_relative 'urlmap'
292
291
 
293
292
  mapped = default_app ? {'/' => default_app} : {}
294
293
  mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma::Rack
2
4
  # Rack::URLMap takes a hash mapping urls or paths to apps, and
3
5
  # dispatches accordingly. Support for HTTP/1.1 host names exists if
@@ -1,4 +1,6 @@
1
- require 'rack/handler/puma'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../rack/handler/puma'
2
4
 
3
5
  module Rack::Handler
4
6
  def self.default(options = {})
data/lib/puma/reactor.rb CHANGED
@@ -1,213 +1,116 @@
1
- require 'puma/util'
2
- require 'puma/minissl'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'queue_close' unless ::Queue.instance_methods.include? :close
3
4
 
4
5
  module Puma
6
+ class UnsupportedBackend < StandardError; end
7
+
8
+ # Monitors a collection of IO objects, calling a block whenever
9
+ # any monitored object either receives data or times out, or when the Reactor shuts down.
10
+ #
11
+ # The waiting/wake up is performed with nio4r, which will use the appropriate backend (libev,
12
+ # Java NIO or just plain IO#select). The call to `NIO::Selector#select` will
13
+ # 'wakeup' any IO object that receives data.
14
+ #
15
+ # This class additionally tracks a timeout for every added object,
16
+ # and wakes up any object when its timeout elapses.
17
+ #
18
+ # The implementation uses a Queue to synchronize adding new objects from the internal select loop.
5
19
  class Reactor
6
- DefaultSleepFor = 5
7
-
8
- def initialize(server, app_pool)
9
- @server = server
10
- @events = server.events
11
- @app_pool = app_pool
12
-
13
- @mutex = Mutex.new
14
- @ready, @trigger = Puma::Util.pipe
15
- @input = []
16
- @sleep_for = DefaultSleepFor
20
+ # Create a new Reactor to monitor IO objects added by #add.
21
+ # The provided block will be invoked when an IO has data available to read,
22
+ # its timeout elapses, or when the Reactor shuts down.
23
+ def initialize(backend, &block)
24
+ require 'nio'
25
+ unless backend == :auto || NIO::Selector.backends.include?(backend)
26
+ raise "unsupported IO selector backend: #{backend} (available backends: #{NIO::Selector.backends.join(', ')})"
27
+ end
28
+ @selector = backend == :auto ? NIO::Selector.new : NIO::Selector.new(backend)
29
+ @input = Queue.new
17
30
  @timeouts = []
18
-
19
- @sockets = [@ready]
31
+ @block = block
20
32
  end
21
33
 
22
- private
23
-
24
- def run_internal
25
- sockets = @sockets
26
-
27
- while true
28
- begin
29
- ready = IO.select sockets, nil, nil, @sleep_for
30
- rescue IOError => e
31
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
32
- if sockets.any? { |socket| socket.closed? }
33
- STDERR.puts "Error in select: #{e.message} (#{e.class})"
34
- STDERR.puts e.backtrace
35
- sockets = sockets.reject { |socket| socket.closed? }
36
- retry
37
- else
38
- raise
39
- end
40
- end
41
-
42
- if ready and reads = ready[0]
43
- reads.each do |c|
44
- if c == @ready
45
- @mutex.synchronize do
46
- case @ready.read(1)
47
- when "*"
48
- sockets += @input
49
- @input.clear
50
- when "c"
51
- sockets.delete_if do |s|
52
- if s == @ready
53
- false
54
- else
55
- s.close
56
- true
57
- end
58
- end
59
- when "!"
60
- return
61
- end
62
- end
63
- else
64
- # We have to be sure to remove it from the timeout
65
- # list or we'll accidentally close the socket when
66
- # it's in use!
67
- if c.timeout_at
68
- @mutex.synchronize do
69
- @timeouts.delete c
70
- end
71
- end
72
-
73
- begin
74
- if c.try_to_finish
75
- @app_pool << c
76
- sockets.delete c
77
- end
78
-
79
- # Don't report these to the lowlevel_error handler, otherwise
80
- # will be flooding them with errors when persistent connections
81
- # are closed.
82
- rescue ConnectionError
83
- c.write_500
84
- c.close
85
-
86
- sockets.delete c
87
-
88
- # SSL handshake failure
89
- rescue MiniSSL::SSLError => e
90
- @server.lowlevel_error(e, c.env)
91
-
92
- ssl_socket = c.io
93
- addr = ssl_socket.peeraddr.last
94
- cert = ssl_socket.peercert
95
-
96
- c.close
97
- sockets.delete c
98
-
99
- @events.ssl_error @server, addr, cert, e
100
-
101
- # The client doesn't know HTTP well
102
- rescue HttpParserError => e
103
- @server.lowlevel_error(e, c.env)
104
-
105
- c.write_400
106
- c.close
107
-
108
- sockets.delete c
109
-
110
- @events.parse_error @server, c.env, e
111
- rescue StandardError => e
112
- @server.lowlevel_error(e, c.env)
113
-
114
- c.write_500
115
- c.close
116
-
117
- sockets.delete c
118
- end
119
- end
120
- end
121
- end
122
-
123
- unless @timeouts.empty?
124
- @mutex.synchronize do
125
- now = Time.now
126
-
127
- while @timeouts.first.timeout_at < now
128
- c = @timeouts.shift
129
- c.write_408 if c.in_data_phase
130
- c.close
131
- sockets.delete c
132
-
133
- break if @timeouts.empty?
134
- end
135
-
136
- calculate_sleep
137
- end
34
+ # Run the internal select loop, using a background thread by default.
35
+ def run(background=true)
36
+ if background
37
+ @thread = Thread.new do
38
+ Puma.set_thread_name "reactor"
39
+ select_loop
138
40
  end
41
+ else
42
+ select_loop
139
43
  end
140
44
  end
141
45
 
142
- public
143
-
144
- def run
145
- run_internal
146
- ensure
147
- @trigger.close
148
- @ready.close
46
+ # Add a new client to monitor.
47
+ # The object must respond to #timeout and #timeout_at.
48
+ # Returns false if the reactor is already shut down.
49
+ def add(client)
50
+ @input << client
51
+ @selector.wakeup
52
+ true
53
+ rescue ClosedQueueError, IOError # Ignore if selector is already closed
54
+ false
149
55
  end
150
56
 
151
- def run_in_thread
152
- @thread = Thread.new do
153
- begin
154
- run_internal
155
- rescue StandardError => e
156
- STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
157
- STDERR.puts e.backtrace
158
- retry
159
- ensure
160
- @trigger.close
161
- @ready.close
162
- end
163
- end
164
- end
165
-
166
- def calculate_sleep
167
- if @timeouts.empty?
168
- @sleep_for = DefaultSleepFor
169
- else
170
- diff = @timeouts.first.timeout_at.to_f - Time.now.to_f
171
-
172
- if diff < 0.0
173
- @sleep_for = 0
174
- else
175
- @sleep_for = diff
176
- end
57
+ # Shutdown the reactor, blocking until the background thread is finished.
58
+ def shutdown
59
+ @input.close
60
+ begin
61
+ @selector.wakeup
62
+ rescue IOError # Ignore if selector is already closed
177
63
  end
64
+ @thread&.join
178
65
  end
179
66
 
180
- def add(c)
181
- @mutex.synchronize do
182
- @input << c
183
- @trigger << "*"
184
-
185
- if c.timeout_at
186
- @timeouts << c
187
- @timeouts.sort! { |a,b| a.timeout_at <=> b.timeout_at }
67
+ private
188
68
 
189
- calculate_sleep
69
+ def select_loop
70
+ begin
71
+ until @input.closed? && @input.empty?
72
+ # Wakeup any registered object that receives incoming data.
73
+ # Block until the earliest timeout or Selector#wakeup is called.
74
+ timeout = (earliest = @timeouts.first) && earliest.timeout
75
+ @selector.select(timeout) {|mon| wakeup!(mon.value)}
76
+
77
+ # Wakeup all objects that timed out.
78
+ timed_out = @timeouts.take_while {|t| t.timeout == 0}
79
+ timed_out.each { |c| wakeup! c }
80
+
81
+ unless @input.empty?
82
+ until @input.empty?
83
+ client = @input.pop
84
+ register(client) if client.io_ok?
85
+ end
86
+ @timeouts.sort_by!(&:timeout_at)
87
+ end
190
88
  end
89
+ rescue StandardError => e
90
+ STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
91
+ STDERR.puts e.backtrace
92
+ retry
191
93
  end
94
+ # Wakeup all remaining objects on shutdown.
95
+ @timeouts.each(&@block)
96
+ @selector.close
192
97
  end
193
98
 
194
- # Close all watched sockets and clear them from being watched
195
- def clear!
196
- begin
197
- @trigger << "c"
198
- rescue IOError
199
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
200
- end
99
+ # Start monitoring the object.
100
+ def register(client)
101
+ @selector.register(client.to_io, :r).value = client
102
+ @timeouts << client
103
+ rescue ArgumentError
104
+ # unreadable clients raise error when processed by NIO
201
105
  end
202
106
 
203
- def shutdown
204
- begin
205
- @trigger << "!"
206
- rescue IOError
207
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
107
+ # 'Wake up' a monitored object by calling the provided block.
108
+ # Stop monitoring the object if the block returns `true`.
109
+ def wakeup!(client)
110
+ if @block.call client
111
+ @selector.deregister client.to_io
112
+ @timeouts.delete client
208
113
  end
209
-
210
- @thread.join
211
114
  end
212
115
  end
213
116
  end