puma 4.3.1 → 5.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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +94 -3
  3. data/LICENSE +23 -20
  4. data/README.md +26 -13
  5. data/docs/architecture.md +3 -3
  6. data/docs/deployment.md +9 -3
  7. data/docs/fork_worker.md +31 -0
  8. data/docs/jungle/README.md +13 -0
  9. data/{tools → docs}/jungle/rc.d/README.md +0 -0
  10. data/{tools → docs}/jungle/rc.d/puma +0 -0
  11. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  12. data/{tools → docs}/jungle/upstart/README.md +0 -0
  13. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  14. data/{tools → docs}/jungle/upstart/puma.conf +0 -0
  15. data/docs/signals.md +7 -6
  16. data/docs/systemd.md +1 -63
  17. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  18. data/ext/puma_http11/extconf.rb +4 -3
  19. data/ext/puma_http11/http11_parser.c +3 -1
  20. data/ext/puma_http11/http11_parser.rl +3 -1
  21. data/ext/puma_http11/mini_ssl.c +15 -2
  22. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  23. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  24. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
  25. data/ext/puma_http11/puma_http11.c +7 -38
  26. data/lib/puma.rb +17 -0
  27. data/lib/puma/app/status.rb +18 -3
  28. data/lib/puma/binder.rb +88 -68
  29. data/lib/puma/cli.rb +7 -15
  30. data/lib/puma/client.rb +67 -14
  31. data/lib/puma/cluster.rb +191 -74
  32. data/lib/puma/commonlogger.rb +2 -2
  33. data/lib/puma/configuration.rb +31 -42
  34. data/lib/puma/const.rb +4 -3
  35. data/lib/puma/control_cli.rb +29 -17
  36. data/lib/puma/detect.rb +17 -0
  37. data/lib/puma/dsl.rb +144 -70
  38. data/lib/puma/error_logger.rb +97 -0
  39. data/lib/puma/events.rb +35 -31
  40. data/lib/puma/io_buffer.rb +9 -2
  41. data/lib/puma/jruby_restart.rb +0 -58
  42. data/lib/puma/launcher.rb +49 -31
  43. data/lib/puma/minissl.rb +60 -18
  44. data/lib/puma/minissl/context_builder.rb +0 -3
  45. data/lib/puma/null_io.rb +1 -1
  46. data/lib/puma/plugin.rb +1 -10
  47. data/lib/puma/rack/builder.rb +0 -4
  48. data/lib/puma/reactor.rb +9 -4
  49. data/lib/puma/runner.rb +8 -36
  50. data/lib/puma/server.rb +149 -186
  51. data/lib/puma/single.rb +7 -64
  52. data/lib/puma/state_file.rb +6 -3
  53. data/lib/puma/thread_pool.rb +94 -49
  54. data/lib/rack/handler/puma.rb +1 -3
  55. data/tools/{docker/Dockerfile → Dockerfile} +0 -0
  56. metadata +21 -23
  57. data/docs/tcp_mode.md +0 -96
  58. data/ext/puma_http11/io_buffer.c +0 -155
  59. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  60. data/lib/puma/tcp_logger.rb +0 -41
  61. data/tools/jungle/README.md +0 -19
  62. data/tools/jungle/init.d/README.md +0 -61
  63. data/tools/jungle/init.d/puma +0 -421
  64. data/tools/jungle/init.d/run-puma +0 -18
@@ -14,11 +14,9 @@ module Puma
14
14
  # that this inherits from.
15
15
  class Single < Runner
16
16
  def stats
17
- b = @server.backlog || 0
18
- r = @server.running || 0
19
- t = @server.pool_capacity || 0
20
- m = @server.max_threads || 0
21
- %Q!{ "started_at": "#{@started_at.utc.iso8601}", "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
17
+ {
18
+ started_at: @started_at.utc.iso8601
19
+ }.merge(@server.stats)
22
20
  end
23
21
 
24
22
  def restart
@@ -39,64 +37,10 @@ module Puma
39
37
  @server.stop(true) if @server
40
38
  end
41
39
 
42
- def jruby_daemon?
43
- daemon? and Puma.jruby?
44
- end
45
-
46
- def jruby_daemon_start
47
- require 'puma/jruby_restart'
48
- JRubyRestart.daemon_start(@restart_dir, @launcher.restart_args)
49
- end
50
-
51
40
  def run
52
- already_daemon = false
53
-
54
- if jruby_daemon?
55
- require 'puma/jruby_restart'
56
-
57
- if JRubyRestart.daemon?
58
- # load and bind before redirecting IO so errors show up on stdout/stderr
59
- load_and_bind
60
- redirect_io
61
- end
62
-
63
- already_daemon = JRubyRestart.daemon_init
64
- end
65
-
66
41
  output_header "single"
67
42
 
68
- if jruby_daemon?
69
- if already_daemon
70
- JRubyRestart.perm_daemonize
71
- else
72
- pid = nil
73
-
74
- Signal.trap "SIGUSR2" do
75
- log "* Started new process #{pid} as daemon..."
76
-
77
- # Must use exit! so we don't unwind and run the ensures
78
- # that will be run by the new child (such as deleting the
79
- # pidfile)
80
- exit!(true)
81
- end
82
-
83
- Signal.trap "SIGCHLD" do
84
- log "! Error starting new process as daemon, exiting"
85
- exit 1
86
- end
87
-
88
- jruby_daemon_start
89
- sleep
90
- end
91
- else
92
- if daemon?
93
- log "* Daemonizing..."
94
- Process.daemon(true)
95
- redirect_io
96
- end
97
-
98
- load_and_bind
99
- end
43
+ load_and_bind
100
44
 
101
45
  Plugins.fire_background
102
46
 
@@ -106,10 +50,9 @@ module Puma
106
50
 
107
51
  @server = server = start_server
108
52
 
109
- unless daemon?
110
- log "Use Ctrl-C to stop"
111
- redirect_io
112
- end
53
+
54
+ log "Use Ctrl-C to stop"
55
+ redirect_io
113
56
 
114
57
  @launcher.events.fire_on_booted!
115
58
 
@@ -8,15 +8,18 @@ module Puma
8
8
  @options = {}
9
9
  end
10
10
 
11
- def save(path)
12
- File.write path, YAML.dump(@options)
11
+ def save(path, permission = nil)
12
+ File.open(path, "w") do |file|
13
+ file.chmod(permission) if permission
14
+ file.write(YAML.dump(@options))
15
+ end
13
16
  end
14
17
 
15
18
  def load(path)
16
19
  @options = YAML.load File.read(path)
17
20
  end
18
21
 
19
- FIELDS = %w!control_url control_auth_token pid!
22
+ FIELDS = %w!control_url control_auth_token pid running_from!
20
23
 
21
24
  FIELDS.each do |f|
22
25
  define_method f do
@@ -47,6 +47,7 @@ module Puma
47
47
  @shutdown = false
48
48
 
49
49
  @trim_requested = 0
50
+ @out_of_band_pending = false
50
51
 
51
52
  @workers = []
52
53
 
@@ -54,7 +55,10 @@ module Puma
54
55
  @reaper = nil
55
56
 
56
57
  @mutex.synchronize do
57
- @min.times { spawn_thread }
58
+ @min.times do
59
+ spawn_thread
60
+ @not_full.wait(@mutex)
61
+ end
58
62
  end
59
63
 
60
64
  @clean_thread_locals = false
@@ -62,6 +66,7 @@ module Puma
62
66
 
63
67
  attr_reader :spawned, :trim_requested, :waiting
64
68
  attr_accessor :clean_thread_locals
69
+ attr_accessor :out_of_band_hook # @version 5.0.0
65
70
 
66
71
  def self.clean_thread_locals
67
72
  Thread.current.keys.each do |key| # rubocop: disable Performance/HashEachMethods
@@ -72,13 +77,18 @@ module Puma
72
77
  # How many objects have yet to be processed by the pool?
73
78
  #
74
79
  def backlog
75
- @mutex.synchronize { @todo.size }
80
+ with_mutex { @todo.size }
76
81
  end
77
82
 
78
83
  def pool_capacity
79
84
  waiting + (@max - spawned)
80
85
  end
81
86
 
87
+ # @version 5.0.0
88
+ def busy_threads
89
+ with_mutex { @spawned - @waiting + @todo.size }
90
+ end
91
+
82
92
  # :nodoc:
83
93
  #
84
94
  # Must be called with @mutex held!
@@ -99,48 +109,40 @@ module Puma
99
109
  while true
100
110
  work = nil
101
111
 
102
- continue = true
103
-
104
112
  mutex.synchronize do
105
113
  while todo.empty?
106
114
  if @trim_requested > 0
107
115
  @trim_requested -= 1
108
- continue = false
109
- not_full.signal
110
- break
111
- end
112
-
113
- if @shutdown
114
- continue = false
115
- break
116
+ @spawned -= 1
117
+ @workers.delete th
118
+ Thread.exit
116
119
  end
117
120
 
118
121
  @waiting += 1
122
+ if @out_of_band_pending && trigger_out_of_band_hook
123
+ @out_of_band_pending = false
124
+ end
119
125
  not_full.signal
120
- not_empty.wait mutex
121
- @waiting -= 1
126
+ begin
127
+ not_empty.wait mutex
128
+ ensure
129
+ @waiting -= 1
130
+ end
122
131
  end
123
132
 
124
- work = todo.shift if continue
133
+ work = todo.shift
125
134
  end
126
135
 
127
- break unless continue
128
-
129
136
  if @clean_thread_locals
130
137
  ThreadPool.clean_thread_locals
131
138
  end
132
139
 
133
140
  begin
134
- block.call(work, *extra)
141
+ @out_of_band_pending = true if block.call(work, *extra)
135
142
  rescue Exception => e
136
143
  STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
137
144
  end
138
145
  end
139
-
140
- mutex.synchronize do
141
- @spawned -= 1
142
- @workers.delete th
143
- end
144
146
  end
145
147
 
146
148
  @workers << th
@@ -150,9 +152,32 @@ module Puma
150
152
 
151
153
  private :spawn_thread
152
154
 
155
+ # @version 5.0.0
156
+ def trigger_out_of_band_hook
157
+ return false unless out_of_band_hook && out_of_band_hook.any?
158
+
159
+ # we execute on idle hook when all threads are free
160
+ return false unless @spawned == @waiting
161
+
162
+ out_of_band_hook.each(&:call)
163
+ true
164
+ rescue Exception => e
165
+ STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
166
+ true
167
+ end
168
+
169
+ private :trigger_out_of_band_hook
170
+
171
+ # @version 5.0.0
172
+ def with_mutex(&block)
173
+ @mutex.owned? ?
174
+ yield :
175
+ @mutex.synchronize(&block)
176
+ end
177
+
153
178
  # Add +work+ to the todo list for a Thread to pickup and process.
154
179
  def <<(work)
155
- @mutex.synchronize do
180
+ with_mutex do
156
181
  if @shutdown
157
182
  raise "Unable to add work while shutting down"
158
183
  end
@@ -193,11 +218,8 @@ module Puma
193
218
  # method would not block and another request would be added into the reactor
194
219
  # by the server. This would continue until a fully bufferend request
195
220
  # makes it through the reactor and can then be processed by the thread pool.
196
- #
197
- # Returns the current number of busy threads, or +nil+ if shutting down.
198
- #
199
221
  def wait_until_not_full
200
- @mutex.synchronize do
222
+ with_mutex do
201
223
  while true
202
224
  return if @shutdown
203
225
 
@@ -205,21 +227,41 @@ module Puma
205
227
  # is work queued that cannot be handled by waiting
206
228
  # threads, then accept more work until we would
207
229
  # spin up the max number of threads.
208
- busy_threads = @spawned - @waiting + @todo.size
209
- return busy_threads if @max > busy_threads
230
+ return if busy_threads < @max
210
231
 
211
232
  @not_full.wait @mutex
212
233
  end
213
234
  end
214
235
  end
215
236
 
216
- # If too many threads are in the pool, tell one to finish go ahead
237
+ # @version 5.0.0
238
+ def wait_for_less_busy_worker(delay_s)
239
+ # Ruby MRI does GVL, this can result
240
+ # in processing contention when multiple threads
241
+ # (requests) are running concurrently
242
+ return unless Puma.mri?
243
+ return unless delay_s > 0
244
+
245
+ with_mutex do
246
+ return if @shutdown
247
+
248
+ # do not delay, if we are not busy
249
+ return unless busy_threads > 0
250
+
251
+ # this will be signaled once a request finishes,
252
+ # which can happen earlier than delay
253
+ @not_full.wait @mutex, delay_s
254
+ end
255
+ end
256
+
257
+ # If there are any free threads in the pool, tell one to go ahead
217
258
  # and exit. If +force+ is true, then a trim request is requested
218
259
  # even if all threads are being utilized.
219
260
  #
220
261
  def trim(force=false)
221
- @mutex.synchronize do
222
- if (force or @waiting > 0) and @spawned - @trim_requested > @min
262
+ with_mutex do
263
+ free = @waiting - @todo.size
264
+ if (force or free > 0) and @spawned - @trim_requested > @min
223
265
  @trim_requested += 1
224
266
  @not_empty.signal
225
267
  end
@@ -229,7 +271,7 @@ module Puma
229
271
  # If there are dead threads in the pool make them go away while decreasing
230
272
  # spawned counter so that new healthy threads could be created again.
231
273
  def reap
232
- @mutex.synchronize do
274
+ with_mutex do
233
275
  dead_workers = @workers.reject(&:alive?)
234
276
 
235
277
  dead_workers.each do |worker|
@@ -281,10 +323,14 @@ module Puma
281
323
  end
282
324
 
283
325
  # Tell all threads in the pool to exit and wait for them to finish.
326
+ # Wait +timeout+ seconds then raise +ForceShutdown+ in remaining threads.
327
+ # Next, wait an extra +grace+ seconds then force-kill remaining threads.
328
+ # Finally, wait +kill_grace+ seconds for remaining threads to exit.
284
329
  #
285
330
  def shutdown(timeout=-1)
286
- threads = @mutex.synchronize do
331
+ threads = with_mutex do
287
332
  @shutdown = true
333
+ @trim_requested = @spawned
288
334
  @not_empty.broadcast
289
335
  @not_full.broadcast
290
336
 
@@ -298,27 +344,26 @@ module Puma
298
344
  # Wait for threads to finish without force shutdown.
299
345
  threads.each(&:join)
300
346
  else
301
- # Wait for threads to finish after n attempts (+timeout+).
302
- # If threads are still running, it will forcefully kill them.
303
- timeout.times do
304
- threads.delete_if do |t|
305
- t.join 1
306
- end
307
-
308
- if threads.empty?
309
- break
310
- else
311
- sleep 1
347
+ join = ->(inner_timeout) do
348
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
349
+ threads.reject! do |t|
350
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
351
+ t.join inner_timeout - elapsed
312
352
  end
313
353
  end
314
354
 
355
+ # Wait +timeout+ seconds for threads to finish.
356
+ join.call(timeout)
357
+
358
+ # If threads are still running, raise ForceShutdown and wait to finish.
315
359
  threads.each do |t|
316
360
  t.raise ForceShutdown
317
361
  end
362
+ join.call(SHUTDOWN_GRACE_TIME)
318
363
 
319
- threads.each do |t|
320
- t.join SHUTDOWN_GRACE_TIME
321
- end
364
+ # If threads are _still_ running, forcefully kill them and wait to finish.
365
+ threads.each(&:kill)
366
+ join.call(1)
322
367
  end
323
368
 
324
369
  @spawned = 0
@@ -30,8 +30,6 @@ module Rack
30
30
  end
31
31
 
32
32
  conf = ::Puma::Configuration.new(options, default_options) do |user_config, file_config, default_config|
33
- user_config.quiet
34
-
35
33
  if options.delete(:Verbose)
36
34
  app = Rack::CommonLogger.new(app, STDOUT)
37
35
  end
@@ -61,7 +59,7 @@ module Rack
61
59
  conf
62
60
  end
63
61
 
64
- def self.run(app, options = {})
62
+ def self.run(app, **options)
65
63
  conf = self.config(app, options)
66
64
 
67
65
  events = options.delete(:Silent) ? ::Puma::Events.strings : ::Puma::Events.stdio
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: 4.3.1
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Phoenix
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-05 00:00:00.000000000 Z
11
+ date: 2020-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nio4r
@@ -45,15 +45,22 @@ files:
45
45
  - bin/pumactl
46
46
  - docs/architecture.md
47
47
  - docs/deployment.md
48
+ - docs/fork_worker.md
48
49
  - docs/images/puma-connection-flow-no-reactor.png
49
50
  - docs/images/puma-connection-flow.png
50
51
  - docs/images/puma-general-arch.png
52
+ - docs/jungle/README.md
53
+ - docs/jungle/rc.d/README.md
54
+ - docs/jungle/rc.d/puma
55
+ - docs/jungle/rc.d/puma.conf
56
+ - docs/jungle/upstart/README.md
57
+ - docs/jungle/upstart/puma-manager.conf
58
+ - docs/jungle/upstart/puma.conf
51
59
  - docs/nginx.md
52
60
  - docs/plugins.md
53
61
  - docs/restart.md
54
62
  - docs/signals.md
55
63
  - docs/systemd.md
56
- - docs/tcp_mode.md
57
64
  - ext/puma_http11/PumaHttp11Service.java
58
65
  - ext/puma_http11/ext_help.h
59
66
  - ext/puma_http11/extconf.rb
@@ -62,11 +69,10 @@ files:
62
69
  - ext/puma_http11/http11_parser.java.rl
63
70
  - ext/puma_http11/http11_parser.rl
64
71
  - ext/puma_http11/http11_parser_common.rl
65
- - ext/puma_http11/io_buffer.c
66
72
  - ext/puma_http11/mini_ssl.c
73
+ - ext/puma_http11/no_ssl/PumaHttp11Service.java
67
74
  - ext/puma_http11/org/jruby/puma/Http11.java
68
75
  - ext/puma_http11/org/jruby/puma/Http11Parser.java
69
- - ext/puma_http11/org/jruby/puma/IOBuffer.java
70
76
  - ext/puma_http11/org/jruby/puma/MiniSSL.java
71
77
  - ext/puma_http11/puma_http11.c
72
78
  - lib/puma.rb
@@ -82,6 +88,7 @@ files:
82
88
  - lib/puma/control_cli.rb
83
89
  - lib/puma/detect.rb
84
90
  - lib/puma/dsl.rb
91
+ - lib/puma/error_logger.rb
85
92
  - lib/puma/events.rb
86
93
  - lib/puma/io_buffer.rb
87
94
  - lib/puma/jruby_restart.rb
@@ -99,29 +106,20 @@ files:
99
106
  - lib/puma/server.rb
100
107
  - lib/puma/single.rb
101
108
  - lib/puma/state_file.rb
102
- - lib/puma/tcp_logger.rb
103
109
  - lib/puma/thread_pool.rb
104
110
  - lib/puma/util.rb
105
111
  - lib/rack/handler/puma.rb
106
- - tools/docker/Dockerfile
107
- - tools/jungle/README.md
108
- - tools/jungle/init.d/README.md
109
- - tools/jungle/init.d/puma
110
- - tools/jungle/init.d/run-puma
111
- - tools/jungle/rc.d/README.md
112
- - tools/jungle/rc.d/puma
113
- - tools/jungle/rc.d/puma.conf
114
- - tools/jungle/upstart/README.md
115
- - tools/jungle/upstart/puma-manager.conf
116
- - tools/jungle/upstart/puma.conf
112
+ - tools/Dockerfile
117
113
  - tools/trickletest.rb
118
- homepage: http://puma.io
114
+ homepage: https://puma.io
119
115
  licenses:
120
116
  - BSD-3-Clause
121
117
  metadata:
122
- msys2_mingw_dependencies: openssl
118
+ bug_tracker_uri: https://github.com/puma/puma/issues
123
119
  changelog_uri: https://github.com/puma/puma/blob/master/History.md
124
- post_install_message:
120
+ homepage_uri: https://puma.io
121
+ source_code_uri: https://github.com/puma/puma
122
+ post_install_message:
125
123
  rdoc_options: []
126
124
  require_paths:
127
125
  - lib
@@ -136,8 +134,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
134
  - !ruby/object:Gem::Version
137
135
  version: '0'
138
136
  requirements: []
139
- rubygems_version: 3.0.3
140
- signing_key:
137
+ rubygems_version: 3.1.2
138
+ signing_key:
141
139
  specification_version: 4
142
140
  summary: Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for
143
141
  Ruby/Rack applications