puma 7.2.0-java → 8.0.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ module Puma
2
+ # ServerPluginControl provides a control interface for server plugins to
3
+ # interact with and manage server settings dynamically.
4
+ #
5
+ # This class acts as a facade between plugins and the Puma server,
6
+ # allowing plugins to safely modify server configuration and thread pool
7
+ # settings without direct access to the server's internal state.
8
+ #
9
+ class ServerPluginControl
10
+ def initialize(server)
11
+ @server = server
12
+ end
13
+
14
+ # Returns the maximum number of threads in the thread pool.
15
+ def max_threads
16
+ @server.max_threads
17
+ end
18
+
19
+ # Returns the minimum number of threads in the thread pool.
20
+ def min_threads
21
+ @server.min_threads
22
+ end
23
+
24
+ # Updates the minimum and maximum number of threads in the thread pool.
25
+ #
26
+ # @see Puma::Server#update_thread_pool_min_max
27
+ #
28
+ def update_thread_pool_min_max(min: max_threads, max: min_threads)
29
+ @server.update_thread_pool_min_max(min: min, max: max)
30
+ end
31
+ end
32
+ end
@@ -3,6 +3,7 @@
3
3
  require 'thread'
4
4
 
5
5
  require_relative 'io_buffer'
6
+ require_relative 'server_plugin_control'
6
7
 
7
8
  module Puma
8
9
 
@@ -24,6 +25,51 @@ module Puma
24
25
  class ForceShutdown < RuntimeError
25
26
  end
26
27
 
28
+ class ProcessorThread
29
+ attr_accessor :thread
30
+ attr_writer :marked_as_io_thread
31
+
32
+ def initialize(pool)
33
+ @pool = pool
34
+ @thread = nil
35
+ @marked_as_io_thread = false
36
+ end
37
+
38
+ def mark_as_io_thread!
39
+ unless @marked_as_io_thread
40
+ @marked_as_io_thread = true
41
+
42
+ # Immediately signal the pool that it can spawn a new thread
43
+ # if there's some work in the queue.
44
+ @pool.spawn_thread_if_needed
45
+ end
46
+ end
47
+
48
+ def marked_as_io_thread?
49
+ @marked_as_io_thread
50
+ end
51
+
52
+ def alive?
53
+ @thread&.alive?
54
+ end
55
+
56
+ def join(...)
57
+ @thread.join(...)
58
+ end
59
+
60
+ def kill(...)
61
+ @thread.kill(...)
62
+ end
63
+
64
+ def [](key)
65
+ @thread[key]
66
+ end
67
+
68
+ def raise(...)
69
+ @thread.raise(...)
70
+ end
71
+ end
72
+
27
73
  # How long, after raising the ForceShutdown of a thread during
28
74
  # forced shutdown mode, to wait for the thread to try and finish
29
75
  # up its work before leaving the thread to die on the vine.
@@ -52,10 +98,13 @@ module Puma
52
98
  @name = name
53
99
  @min = Integer(options[:min_threads])
54
100
  @max = Integer(options[:max_threads])
101
+ @max_io_threads = Integer(options[:max_io_threads] || 0)
102
+
55
103
  # Not an 'exposed' option, options[:pool_shutdown_grace_time] is used in CI
56
104
  # to shorten @shutdown_grace_time from SHUTDOWN_GRACE_TIME. Parallel CI
57
105
  # makes stubbing constants difficult.
58
106
  @shutdown_grace_time = Float(options[:pool_shutdown_grace_time] || SHUTDOWN_GRACE_TIME)
107
+ @shutdown_debug = options[:shutdown_debug]
59
108
  @block = block
60
109
  @out_of_band = options[:out_of_band]
61
110
  @out_of_band_running = false
@@ -70,7 +119,7 @@ module Puma
70
119
  @trim_requested = 0
71
120
  @out_of_band_pending = false
72
121
 
73
- @workers = []
122
+ @processors = []
74
123
 
75
124
  @auto_trim = nil
76
125
  @reaper = nil
@@ -87,6 +136,7 @@ module Puma
87
136
  end
88
137
 
89
138
  attr_reader :spawned, :trim_requested, :waiting
139
+ attr_accessor :min, :max
90
140
 
91
141
  # generate stats hash so as not to perform multiple locks
92
142
  # @return [Hash] hash containing stat info from ThreadPool
@@ -96,8 +146,9 @@ module Puma
96
146
  @backlog_max = 0
97
147
  { backlog: @todo.size,
98
148
  running: @spawned,
99
- pool_capacity: @waiting + (@max - @spawned),
149
+ pool_capacity: pool_capacity,
100
150
  busy_threads: @spawned - @waiting + @todo.size,
151
+ io_threads: @processors.count(&:marked_as_io_thread?),
101
152
  backlog_max: temp
102
153
  }
103
154
  end
@@ -121,7 +172,7 @@ module Puma
121
172
 
122
173
  # @!attribute [r] pool_capacity
123
174
  def pool_capacity
124
- waiting + (@max - spawned)
175
+ (waiting + (@max - spawned)).clamp(0, Float::INFINITY)
125
176
  end
126
177
 
127
178
  # @!attribute [r] busy_threads
@@ -138,7 +189,8 @@ module Puma
138
189
  @spawned += 1
139
190
 
140
191
  trigger_before_thread_start_hooks
141
- th = Thread.new(@spawned) do |spawned|
192
+ processor = ProcessorThread.new(self)
193
+ processor.thread = Thread.new(processor, @spawned) do |processor, spawned|
142
194
  Puma.set_thread_name '%s tp %03i' % [@name, spawned]
143
195
  # Advertise server into the thread
144
196
  Thread.current.puma_server = @server
@@ -153,11 +205,23 @@ module Puma
153
205
  work = nil
154
206
 
155
207
  mutex.synchronize do
208
+ if processor.marked_as_io_thread?
209
+ if @processors.count { |t| !t.marked_as_io_thread? } < @max
210
+ # We're not at max processor threads, so the io thread can rejoin the normal population.
211
+ processor.marked_as_io_thread = false
212
+ else
213
+ # We're already at max threads, so we exit the extra io thread.
214
+ @processors.delete(processor)
215
+ trigger_before_thread_exit_hooks
216
+ Thread.exit
217
+ end
218
+ end
219
+
156
220
  while todo.empty?
157
221
  if @trim_requested > 0
158
222
  @trim_requested -= 1
159
223
  @spawned -= 1
160
- @workers.delete th
224
+ @processors.delete(processor)
161
225
  not_full.signal
162
226
  trigger_before_thread_exit_hooks
163
227
  Thread.exit
@@ -179,16 +243,16 @@ module Puma
179
243
  end
180
244
 
181
245
  begin
182
- @out_of_band_pending = true if block.call(work)
246
+ @out_of_band_pending = true if block.call(processor, work)
183
247
  rescue Exception => e
184
248
  STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
185
249
  end
186
250
  end
187
251
  end
188
252
 
189
- @workers << th
253
+ @processors << processor
190
254
 
191
- th
255
+ processor
192
256
  end
193
257
 
194
258
  private :spawn_thread
@@ -198,7 +262,7 @@ module Puma
198
262
 
199
263
  @before_thread_start.each do |b|
200
264
  begin
201
- b[:block].call
265
+ b[:block].call(ServerPluginControl.new(@server))
202
266
  rescue Exception => e
203
267
  STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
204
268
  end
@@ -257,6 +321,16 @@ module Puma
257
321
  @mutex.synchronize(&block)
258
322
  end
259
323
 
324
+ # :nodoc:
325
+ #
326
+ # Must be called with @mutex held!
327
+ #
328
+ def can_spawn_processor?
329
+ io_processors_count = @processors.count(&:marked_as_io_thread?)
330
+ extra_io_processors_count = io_processors_count > @max_io_threads ? io_processors_count - @max_io_threads : 0
331
+ (@spawned - io_processors_count) < (@max - extra_io_processors_count)
332
+ end
333
+
260
334
  # Add +work+ to the todo list for a Thread to pickup and process.
261
335
  def <<(work)
262
336
  with_mutex do
@@ -268,12 +342,21 @@ module Puma
268
342
  t = @todo.size
269
343
  @backlog_max = t if t > @backlog_max
270
344
 
271
- if @waiting < @todo.size and @spawned < @max
345
+ if @waiting < @todo.size and can_spawn_processor?
272
346
  spawn_thread
273
347
  end
274
348
 
275
349
  @not_empty.signal
276
350
  end
351
+ self
352
+ end
353
+
354
+ def spawn_thread_if_needed # :nodoc:
355
+ with_mutex do
356
+ if @waiting < @todo.size and can_spawn_processor?
357
+ spawn_thread
358
+ end
359
+ end
277
360
  end
278
361
 
279
362
  # If there are any free threads in the pool, tell one to go ahead
@@ -294,16 +377,12 @@ module Puma
294
377
  # spawned counter so that new healthy threads could be created again.
295
378
  def reap
296
379
  with_mutex do
297
- dead_workers = @workers.reject(&:alive?)
380
+ @processors, dead_processors = @processors.partition(&:alive?)
298
381
 
299
- dead_workers.each do |worker|
300
- worker.kill
382
+ dead_processors.each do |processor|
383
+ processor.kill
301
384
  @spawned -= 1
302
385
  end
303
-
304
- @workers.delete_if do |w|
305
- dead_workers.include?(w)
306
- end
307
386
  end
308
387
  end
309
388
 
@@ -362,7 +441,7 @@ module Puma
362
441
  # Next, wait an extra +@shutdown_grace_time+ seconds then force-kill remaining
363
442
  # threads. Finally, wait 1 second for remaining threads to exit.
364
443
  #
365
- def shutdown(timeout=-1)
444
+ def shutdown(timeout)
366
445
  threads = with_mutex do
367
446
  @shutdown = true
368
447
  @trim_requested = @spawned
@@ -371,8 +450,12 @@ module Puma
371
450
 
372
451
  @auto_trim&.stop
373
452
  @reaper&.stop
374
- # dup workers so that we join them all safely
375
- @workers.dup
453
+ # dup processors so that we join them all safely
454
+ @processors.dup
455
+ end
456
+
457
+ if @shutdown_debug == true
458
+ shutdown_debug("Shutdown initiated")
376
459
  end
377
460
 
378
461
  if timeout == -1
@@ -382,13 +465,16 @@ module Puma
382
465
  join = ->(inner_timeout) do
383
466
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
384
467
  threads.reject! do |t|
385
- elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
386
- t.join inner_timeout - elapsed
468
+ remaining = inner_timeout - (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
469
+ remaining > 0 && t.join(remaining)
387
470
  end
388
471
  end
389
472
 
390
473
  # Wait +timeout+ seconds for threads to finish.
391
474
  join.call(timeout)
475
+ if @shutdown_debug == :on_force && !threads.empty?
476
+ shutdown_debug("Shutdown timeout exceeded")
477
+ end
392
478
 
393
479
  # If threads are still running, raise ForceShutdown and wait to finish.
394
480
  @shutdown_mutex.synchronize do
@@ -398,6 +484,9 @@ module Puma
398
484
  end
399
485
  end
400
486
  join.call(@shutdown_grace_time)
487
+ if @shutdown_debug == :on_force && !threads.empty?
488
+ shutdown_debug("Shutdown grace timeout exceeded")
489
+ end
401
490
 
402
491
  # If threads are _still_ running, forcefully kill them and wait to finish.
403
492
  threads.each(&:kill)
@@ -405,7 +494,24 @@ module Puma
405
494
  end
406
495
 
407
496
  @spawned = 0
408
- @workers = []
497
+ @processors = []
498
+ end
499
+
500
+ private
501
+
502
+ def shutdown_debug(message)
503
+ pid = Process.pid
504
+ threads = Thread.list
505
+
506
+ $stdout.syswrite "#{pid}: #{message}\n"
507
+ $stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n"
508
+
509
+ threads.each_with_index do |thread, index|
510
+ $stdout.syswrite "#{pid}: Thread #{index + 1}/#{threads.size}: #{thread.inspect}\n"
511
+ $stdout.syswrite "#{pid}: #{(thread.backtrace || []).join("\n#{pid}: ")}\n\n"
512
+ end
513
+
514
+ $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
409
515
  end
410
516
  end
411
517
  end
@@ -109,7 +109,7 @@ module Puma
109
109
  end
110
110
 
111
111
  if port
112
- host ||= ::Puma::Configuration::DEFAULTS[:tcp_host]
112
+ host ||= ::Puma::Configuration.default_tcp_host
113
113
  config.port port, host
114
114
  end
115
115
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.2.0
4
+ version: 8.0.0
5
5
  platform: java
6
6
  authors:
7
7
  - Evan Phoenix
@@ -42,13 +42,21 @@ files:
42
42
  - bin/puma
43
43
  - bin/puma-wild
44
44
  - bin/pumactl
45
+ - docs/5.0-Upgrade.md
46
+ - docs/6.0-Upgrade.md
47
+ - docs/7.0-Upgrade.md
48
+ - docs/8.0-Upgrade.md
45
49
  - docs/architecture.md
46
50
  - docs/compile_options.md
47
51
  - docs/deployment.md
48
52
  - docs/fork_worker.md
53
+ - docs/grpc.md
54
+ - docs/images/favicon.svg
49
55
  - docs/images/puma-connection-flow-no-reactor.png
50
56
  - docs/images/puma-connection-flow.png
51
57
  - docs/images/puma-general-arch.png
58
+ - docs/images/running-puma.svg
59
+ - docs/images/standard-logo.svg
52
60
  - docs/java_options.md
53
61
  - docs/jungle/README.md
54
62
  - docs/jungle/rc.d/README.md
@@ -73,6 +81,7 @@ files:
73
81
  - ext/puma_http11/http11_parser_common.rl
74
82
  - ext/puma_http11/mini_ssl.c
75
83
  - ext/puma_http11/no_ssl/PumaHttp11Service.java
84
+ - ext/puma_http11/org/jruby/puma/EnvKey.java
76
85
  - ext/puma_http11/org/jruby/puma/Http11.java
77
86
  - ext/puma_http11/org/jruby/puma/Http11Parser.java
78
87
  - ext/puma_http11/org/jruby/puma/MiniSSL.java
@@ -82,6 +91,7 @@ files:
82
91
  - lib/puma/binder.rb
83
92
  - lib/puma/cli.rb
84
93
  - lib/puma/client.rb
94
+ - lib/puma/client_env.rb
85
95
  - lib/puma/cluster.rb
86
96
  - lib/puma/cluster/worker.rb
87
97
  - lib/puma/cluster/worker_handle.rb
@@ -111,10 +121,11 @@ files:
111
121
  - lib/puma/rack/urlmap.rb
112
122
  - lib/puma/rack_default.rb
113
123
  - lib/puma/reactor.rb
114
- - lib/puma/request.rb
124
+ - lib/puma/response.rb
115
125
  - lib/puma/runner.rb
116
126
  - lib/puma/sd_notify.rb
117
127
  - lib/puma/server.rb
128
+ - lib/puma/server_plugin_control.rb
118
129
  - lib/puma/single.rb
119
130
  - lib/puma/state_file.rb
120
131
  - lib/puma/thread_pool.rb
@@ -146,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
146
157
  - !ruby/object:Gem::Version
147
158
  version: '0'
148
159
  requirements: []
149
- rubygems_version: 3.6.9
160
+ rubygems_version: 3.7.2
150
161
  specification_version: 4
151
162
  summary: A Ruby/Rack web server built for parallelism.
152
163
  test_files: []