puma 6.6.0-java → 7.1.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +170 -5
  3. data/README.md +24 -32
  4. data/docs/fork_worker.md +5 -5
  5. data/docs/kubernetes.md +8 -6
  6. data/docs/restart.md +2 -2
  7. data/docs/signals.md +11 -11
  8. data/docs/stats.md +3 -2
  9. data/docs/systemd.md +1 -1
  10. data/ext/puma_http11/extconf.rb +2 -17
  11. data/ext/puma_http11/mini_ssl.c +18 -8
  12. data/ext/puma_http11/org/jruby/puma/Http11.java +10 -2
  13. data/ext/puma_http11/puma_http11.c +23 -11
  14. data/lib/puma/binder.rb +10 -8
  15. data/lib/puma/cli.rb +3 -5
  16. data/lib/puma/client.rb +95 -61
  17. data/lib/puma/cluster/worker.rb +9 -10
  18. data/lib/puma/cluster/worker_handle.rb +38 -7
  19. data/lib/puma/cluster.rb +41 -26
  20. data/lib/puma/cluster_accept_loop_delay.rb +91 -0
  21. data/lib/puma/commonlogger.rb +3 -3
  22. data/lib/puma/configuration.rb +89 -43
  23. data/lib/puma/const.rb +9 -10
  24. data/lib/puma/control_cli.rb +6 -2
  25. data/lib/puma/detect.rb +2 -0
  26. data/lib/puma/dsl.rb +135 -94
  27. data/lib/puma/error_logger.rb +3 -1
  28. data/lib/puma/events.rb +25 -10
  29. data/lib/puma/io_buffer.rb +8 -4
  30. data/lib/puma/launcher/bundle_pruner.rb +1 -1
  31. data/lib/puma/launcher.rb +52 -48
  32. data/lib/puma/minissl.rb +0 -1
  33. data/lib/puma/plugin/systemd.rb +3 -3
  34. data/lib/puma/puma_http11.jar +0 -0
  35. data/lib/puma/rack/urlmap.rb +1 -1
  36. data/lib/puma/reactor.rb +19 -4
  37. data/lib/puma/request.rb +45 -32
  38. data/lib/puma/runner.rb +8 -17
  39. data/lib/puma/server.rb +111 -61
  40. data/lib/puma/single.rb +5 -2
  41. data/lib/puma/state_file.rb +3 -2
  42. data/lib/puma/thread_pool.rb +47 -82
  43. data/lib/puma/util.rb +0 -7
  44. data/lib/puma.rb +10 -0
  45. data/lib/rack/handler/puma.rb +2 -2
  46. data/tools/Dockerfile +3 -1
  47. metadata +6 -7
data/lib/puma/server.rb CHANGED
@@ -12,15 +12,13 @@ require_relative 'client'
12
12
  require_relative 'binder'
13
13
  require_relative 'util'
14
14
  require_relative 'request'
15
+ require_relative 'configuration'
16
+ require_relative 'cluster_accept_loop_delay'
15
17
 
16
18
  require 'socket'
17
19
  require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
18
20
 
19
21
  module Puma
20
-
21
- # This method was private on Ruby 2.4 but became public on Ruby 2.5+:
22
- Thread.send(:attr_accessor, :puma_server)
23
-
24
22
  # The HTTP Server itself. Serves out a single Rack app.
25
23
  #
26
24
  # This class is used by the `Puma::Single` and `Puma::Cluster` classes
@@ -32,6 +30,14 @@ module Puma
32
30
  #
33
31
  # Each `Puma::Server` will have one reactor and one thread pool.
34
32
  class Server
33
+ module FiberPerRequest
34
+ def handle_request(client, requests)
35
+ Fiber.new do
36
+ super
37
+ end.resume
38
+ end
39
+ end
40
+
35
41
  include Puma::Const
36
42
  include Request
37
43
 
@@ -50,7 +56,6 @@ module Puma
50
56
  attr_accessor :app
51
57
  attr_accessor :binder
52
58
 
53
-
54
59
  # Create a server for the rack app +app+.
55
60
  #
56
61
  # +log_writer+ is a Puma::LogWriter object used to log info and error messages.
@@ -77,6 +82,9 @@ module Puma
77
82
 
78
83
  @thread = nil
79
84
  @thread_pool = nil
85
+ @reactor = nil
86
+
87
+ @env_set_http_version = nil
80
88
 
81
89
  @options = if options.is_a?(UserFileDefaultOptions)
82
90
  options
@@ -94,10 +102,19 @@ module Puma
94
102
  @min_threads = @options[:min_threads]
95
103
  @max_threads = @options[:max_threads]
96
104
  @queue_requests = @options[:queue_requests]
97
- @max_fast_inline = @options[:max_fast_inline]
105
+ @max_keep_alive = @options[:max_keep_alive]
98
106
  @enable_keep_alives = @options[:enable_keep_alives]
107
+ @enable_keep_alives &&= @queue_requests
99
108
  @io_selector_backend = @options[:io_selector_backend]
100
109
  @http_content_length_limit = @options[:http_content_length_limit]
110
+ @cluster_accept_loop_delay = ClusterAcceptLoopDelay.new(
111
+ workers: @options[:workers],
112
+ max_delay: @options[:wait_for_less_busy_worker] || 0 # Real default is in Configuration::DEFAULTS, this is for unit testing
113
+ )
114
+
115
+ if @options[:fiber_per_request]
116
+ singleton_class.prepend(FiberPerRequest)
117
+ end
101
118
 
102
119
  # make this a hash, since we prefer `key?` over `include?`
103
120
  @supported_http_methods =
@@ -114,7 +131,7 @@ module Puma
114
131
  temp = !!(@options[:environment] =~ /\A(development|test)\z/)
115
132
  @leak_stack_on_error = @options[:environment] ? temp : true
116
133
 
117
- @binder = Binder.new(log_writer)
134
+ @binder = Binder.new(log_writer, @options)
118
135
 
119
136
  ENV['RACK_ENV'] ||= "development"
120
137
 
@@ -165,7 +182,6 @@ module Puma
165
182
  begin
166
183
  skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
167
184
  rescue IOError, SystemCallError
168
- Puma::Util.purge_interrupt_queue
169
185
  end
170
186
  end
171
187
 
@@ -174,7 +190,6 @@ module Puma
174
190
  begin
175
191
  skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
176
192
  rescue IOError, SystemCallError
177
- Puma::Util.purge_interrupt_queue
178
193
  end
179
194
  end
180
195
  else
@@ -195,7 +210,6 @@ module Puma
195
210
  begin
196
211
  tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
197
212
  rescue IOError, SystemCallError
198
- Puma::Util.purge_interrupt_queue
199
213
  @precheck_closing = false
200
214
  false
201
215
  else
@@ -220,7 +234,6 @@ module Puma
220
234
  @thread_pool&.spawned
221
235
  end
222
236
 
223
-
224
237
  # This number represents the number of requests that
225
238
  # the server is capable of taking right now.
226
239
  #
@@ -233,11 +246,6 @@ module Puma
233
246
  @thread_pool&.pool_capacity
234
247
  end
235
248
 
236
- # @!attribute [r] busy_threads
237
- def busy_threads
238
- @thread_pool&.busy_threads
239
- end
240
-
241
249
  # Runs the server.
242
250
  #
243
251
  # If +background+ is true (the default) then a thread is spun
@@ -251,10 +259,14 @@ module Puma
251
259
 
252
260
  @status = :run
253
261
 
254
- @thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
262
+ @thread_pool = ThreadPool.new(thread_name, options, server: self) { |client| process_client client }
255
263
 
256
264
  if @queue_requests
257
- @reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
265
+ @reactor = Reactor.new(@io_selector_backend) { |c|
266
+ # Inversion of control, the reactor is calling a method on the server when it
267
+ # is done buffering a request or receives a new request from a keepalive connection.
268
+ self.reactor_wakeup(c)
269
+ }
258
270
  @reactor.run
259
271
  end
260
272
 
@@ -279,6 +291,9 @@ module Puma
279
291
  # This method is called from the Reactor thread when a queued Client receives data,
280
292
  # times out, or when the Reactor is shutting down.
281
293
  #
294
+ # While the code lives in the Server, the logic is executed on the reactor thread, independently
295
+ # from the server.
296
+ #
282
297
  # It is responsible for ensuring that a request has been completely received
283
298
  # before it starts to be processed by the ThreadPool. This may be known as read buffering.
284
299
  # If read buffering is not done, and no other read buffering is performed (such as by an application server
@@ -313,11 +328,14 @@ module Puma
313
328
  end
314
329
  rescue StandardError => e
315
330
  client_error(e, client)
316
- client.close
331
+ close_client_safely(client)
317
332
  true
318
333
  end
319
334
 
320
335
  def handle_servers
336
+ @env_set_http_version = Object.const_defined?(:Rack) && ::Rack.respond_to?(:release) &&
337
+ Gem::Version.new(::Rack.release) < Gem::Version.new('3.1.0')
338
+
321
339
  begin
322
340
  check = @check
323
341
  sockets = [check] + @binder.ios
@@ -364,8 +382,18 @@ module Puma
364
382
  if sock == check
365
383
  break if handle_check
366
384
  else
367
- pool.wait_until_not_full
368
- pool.wait_for_less_busy_worker(options[:wait_for_less_busy_worker]) if @clustered
385
+ # if ThreadPool out_of_band code is running, we don't want to add
386
+ # clients until the code is finished.
387
+ pool.wait_while_out_of_band_running
388
+
389
+ # A well rested herd (cluster) runs faster
390
+ if @cluster_accept_loop_delay.on? && (busy_threads_plus_todo = pool.busy_threads) > 0
391
+ delay = @cluster_accept_loop_delay.calculate(
392
+ max_threads: @max_threads,
393
+ busy_threads_plus_todo: busy_threads_plus_todo
394
+ )
395
+ sleep(delay)
396
+ end
369
397
 
370
398
  io = begin
371
399
  sock.accept_nonblock
@@ -373,11 +401,9 @@ module Puma
373
401
  next
374
402
  end
375
403
  drain += 1 if shutting_down?
376
- pool << Client.new(io, @binder.env(sock)).tap { |c|
377
- c.listener = sock
378
- c.http_content_length_limit = @http_content_length_limit
379
- c.send(addr_send_name, addr_value) if addr_value
380
- }
404
+ client = new_client(io, sock)
405
+ client.send(addr_send_name, addr_value) if addr_value
406
+ pool << client
381
407
  end
382
408
  end
383
409
  rescue IOError, Errno::EBADF
@@ -414,6 +440,14 @@ module Puma
414
440
  @events.fire :state, :done
415
441
  end
416
442
 
443
+ # :nodoc:
444
+ def new_client(io, sock)
445
+ client = Client.new(io, @binder.env(sock))
446
+ client.listener = sock
447
+ client.http_content_length_limit = @http_content_length_limit
448
+ client
449
+ end
450
+
417
451
  # :nodoc:
418
452
  def handle_check
419
453
  cmd = @check.read(1)
@@ -444,17 +478,12 @@ module Puma
444
478
  #
445
479
  # Return true if one or more requests were processed.
446
480
  def process_client(client)
447
- # Advertise this server into the thread
448
- Thread.current.puma_server = self
449
-
450
- clean_thread_locals = options[:clean_thread_locals]
451
481
  close_socket = true
452
482
 
453
483
  requests = 0
454
484
 
455
485
  begin
456
- if @queue_requests &&
457
- !client.eagerly_finish
486
+ if @queue_requests && !client.eagerly_finish
458
487
 
459
488
  client.set_timeout(@first_data_timeout)
460
489
  if @reactor.add client
@@ -467,38 +496,40 @@ module Puma
467
496
  client.finish(@first_data_timeout)
468
497
  end
469
498
 
470
- while true
499
+ can_loop = true
500
+ while can_loop
501
+ can_loop = false
471
502
  @requests_count += 1
472
503
  case handle_request(client, requests + 1)
473
504
  when false
474
- break
475
505
  when :async
476
506
  close_socket = false
477
- break
478
507
  when true
479
- ThreadPool.clean_thread_locals if clean_thread_locals
480
-
481
508
  requests += 1
482
509
 
483
- # As an optimization, try to read the next request from the
484
- # socket for a short time before returning to the reactor.
485
- fast_check = @status == :run
510
+ client.reset
486
511
 
487
- # Always pass the client back to the reactor after a reasonable
488
- # number of inline requests if there are other requests pending.
489
- fast_check = false if requests >= @max_fast_inline &&
490
- @thread_pool.backlog > 0
491
-
492
- next_request_ready = with_force_shutdown(client) do
493
- client.reset(fast_check)
512
+ # This indicates data exists in the client read buffer and there may be
513
+ # additional requests on it, so process them
514
+ next_request_ready = if client.has_back_to_back_requests?
515
+ with_force_shutdown(client) { client.process_back_to_back_requests }
516
+ else
517
+ with_force_shutdown(client) { client.eagerly_finish }
494
518
  end
495
519
 
496
- unless next_request_ready
497
- break unless @queue_requests
520
+ if next_request_ready
521
+ # When Puma has spare threads, allow this one to be monopolized
522
+ # Perf optimization for https://github.com/puma/puma/issues/3788
523
+ if @thread_pool.waiting > 0
524
+ can_loop = true
525
+ else
526
+ @thread_pool << client
527
+ close_socket = false
528
+ end
529
+ elsif @queue_requests
498
530
  client.set_timeout @persistent_timeout
499
531
  if @reactor.add client
500
532
  close_socket = false
501
- break
502
533
  end
503
534
  end
504
535
  end
@@ -511,17 +542,21 @@ module Puma
511
542
  ensure
512
543
  client.io_buffer.reset
513
544
 
514
- begin
515
- client.close if close_socket
516
- rescue IOError, SystemCallError
517
- Puma::Util.purge_interrupt_queue
518
- # Already closed
519
- rescue StandardError => e
520
- @log_writer.unknown_error e, nil, "Client"
521
- end
545
+ close_client_safely(client) if close_socket
522
546
  end
523
547
  end
524
548
 
549
+ # :nodoc:
550
+ def close_client_safely(client)
551
+ client.close
552
+ rescue IOError, SystemCallError
553
+ # Already closed
554
+ rescue MiniSSL::SSLError => e
555
+ @log_writer.ssl_error e, client.io
556
+ rescue StandardError => e
557
+ @log_writer.unknown_error e, nil, "Client"
558
+ end
559
+
525
560
  # Triggers a client timeout if the thread-pool shuts down
526
561
  # during execution of the provided block.
527
562
  def with_force_shutdown(client, &block)
@@ -615,11 +650,10 @@ module Puma
615
650
  @notify << message
616
651
  rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
617
652
  # The server, in another thread, is shutting down
618
- Puma::Util.purge_interrupt_queue
619
653
  rescue RuntimeError => e
620
654
  # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
621
655
  if e.message.include?('IOError')
622
- Puma::Util.purge_interrupt_queue
656
+ # ignore
623
657
  else
624
658
  raise e
625
659
  end
@@ -650,7 +684,16 @@ module Puma
650
684
 
651
685
  # List of methods invoked by #stats.
652
686
  # @version 5.0.0
653
- STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count, :busy_threads].freeze
687
+ STAT_METHODS = [
688
+ :backlog,
689
+ :running,
690
+ :pool_capacity,
691
+ :busy_threads,
692
+ :backlog_max,
693
+ :max_threads,
694
+ :requests_count,
695
+ :reactor_max,
696
+ ].freeze
654
697
 
655
698
  # Returns a hash of stats about the running server for reporting purposes.
656
699
  # @version 5.0.0
@@ -660,9 +703,16 @@ module Puma
660
703
  stats = @thread_pool&.stats || {}
661
704
  stats[:max_threads] = @max_threads
662
705
  stats[:requests_count] = @requests_count
706
+ stats[:reactor_max] = @reactor.reactor_max if @reactor
707
+ reset_max
663
708
  stats
664
709
  end
665
710
 
711
+ def reset_max
712
+ @reactor.reactor_max = 0 if @reactor
713
+ @thread_pool&.reset_max
714
+ end
715
+
666
716
  # below are 'delegations' to binder
667
717
  # remove in Puma 7?
668
718
 
data/lib/puma/single.rb CHANGED
@@ -17,7 +17,7 @@ module Puma
17
17
  def stats
18
18
  {
19
19
  started_at: utc_iso8601(@started_at)
20
- }.merge(@server.stats).merge(super)
20
+ }.merge(@server&.stats || {}).merge(super)
21
21
  end
22
22
 
23
23
  def restart
@@ -53,9 +53,12 @@ module Puma
53
53
  server_thread = server.run
54
54
 
55
55
  log "Use Ctrl-C to stop"
56
+
57
+ warn_ruby_mn_threads
58
+
56
59
  redirect_io
57
60
 
58
- @events.fire_on_booted!
61
+ @events.fire_after_booted!
59
62
 
60
63
  debug_loaded_extensions("Loaded Extensions:") if @log_writer.debug?
61
64
 
@@ -32,10 +32,11 @@ module Puma
32
32
  "#{k}: \"#{v}\"\n" : "#{k}: #{v}\n")
33
33
  end
34
34
  end
35
+
35
36
  if permission
36
- File.write path, contents, mode: 'wb:UTF-8'
37
- else
38
37
  File.write path, contents, mode: 'wb:UTF-8', perm: permission
38
+ else
39
+ File.write path, contents, mode: 'wb:UTF-8'
39
40
  end
40
41
  end
41
42
 
@@ -5,6 +5,10 @@ require 'thread'
5
5
  require_relative 'io_buffer'
6
6
 
7
7
  module Puma
8
+
9
+ # Add `Thread#puma_server` and `Thread#puma_server=`
10
+ Thread.attr_accessor(:puma_server)
11
+
8
12
  # Internal Docs for A simple thread pool management object.
9
13
  #
10
14
  # Each Puma "worker" has a thread pool to process requests.
@@ -25,19 +29,23 @@ module Puma
25
29
  # up its work before leaving the thread to die on the vine.
26
30
  SHUTDOWN_GRACE_TIME = 5 # seconds
27
31
 
32
+ attr_reader :out_of_band_running
33
+
28
34
  # Maintain a minimum of +min+ and maximum of +max+ threads
29
35
  # in the pool.
30
36
  #
31
37
  # The block passed is the work that will be performed in each
32
38
  # thread.
33
39
  #
34
- def initialize(name, options = {}, &block)
40
+ def initialize(name, options = {}, server: nil, &block)
41
+ @server = server
42
+
35
43
  @not_empty = ConditionVariable.new
36
44
  @not_full = ConditionVariable.new
37
45
  @mutex = Mutex.new
46
+ @todo = Queue.new
38
47
 
39
- @todo = []
40
-
48
+ @backlog_max = 0
41
49
  @spawned = 0
42
50
  @waiting = 0
43
51
 
@@ -50,7 +58,8 @@ module Puma
50
58
  @shutdown_grace_time = Float(options[:pool_shutdown_grace_time] || SHUTDOWN_GRACE_TIME)
51
59
  @block = block
52
60
  @out_of_band = options[:out_of_band]
53
- @clean_thread_locals = options[:clean_thread_locals]
61
+ @out_of_band_running = false
62
+ @out_of_band_condvar = ConditionVariable.new
54
63
  @before_thread_start = options[:before_thread_start]
55
64
  @before_thread_exit = options[:before_thread_exit]
56
65
  @reaping_time = options[:reaping_time]
@@ -79,30 +88,37 @@ module Puma
79
88
 
80
89
  attr_reader :spawned, :trim_requested, :waiting
81
90
 
82
- def self.clean_thread_locals
83
- Thread.current.keys.each do |key| # rubocop: disable Style/HashEachMethods
84
- Thread.current[key] = nil unless key == :__recursive_key__
85
- end
86
- end
87
-
88
91
  # generate stats hash so as not to perform multiple locks
89
92
  # @return [Hash] hash containing stat info from ThreadPool
90
93
  def stats
91
94
  with_mutex do
95
+ temp = @backlog_max
96
+ @backlog_max = 0
92
97
  { backlog: @todo.size,
93
98
  running: @spawned,
94
99
  pool_capacity: @waiting + (@max - @spawned),
95
- busy_threads: @spawned - @waiting + @todo.size
100
+ busy_threads: @spawned - @waiting + @todo.size,
101
+ backlog_max: temp
96
102
  }
97
103
  end
98
104
  end
99
105
 
106
+ def reset_max
107
+ with_mutex { @backlog_max = 0 }
108
+ end
109
+
100
110
  # How many objects have yet to be processed by the pool?
101
111
  #
102
112
  def backlog
103
113
  with_mutex { @todo.size }
104
114
  end
105
115
 
116
+ # The maximum size of the backlog
117
+ #
118
+ def backlog_max
119
+ with_mutex { @backlog_max }
120
+ end
121
+
106
122
  # @!attribute [r] pool_capacity
107
123
  def pool_capacity
108
124
  waiting + (@max - spawned)
@@ -124,6 +140,9 @@ module Puma
124
140
  trigger_before_thread_start_hooks
125
141
  th = Thread.new(@spawned) do |spawned|
126
142
  Puma.set_thread_name '%s tp %03i' % [@name, spawned]
143
+ # Advertise server into the thread
144
+ Thread.current.puma_server = @server
145
+
127
146
  todo = @todo
128
147
  block = @block
129
148
  mutex = @mutex
@@ -159,10 +178,6 @@ module Puma
159
178
  work = todo.shift
160
179
  end
161
180
 
162
- if @clean_thread_locals
163
- ThreadPool.clean_thread_locals
164
- end
165
-
166
181
  begin
167
182
  @out_of_band_pending = true if block.call(work)
168
183
  rescue Exception => e
@@ -183,7 +198,7 @@ module Puma
183
198
 
184
199
  @before_thread_start.each do |b|
185
200
  begin
186
- b.call
201
+ b[:block].call
187
202
  rescue Exception => e
188
203
  STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
189
204
  end
@@ -198,7 +213,7 @@ module Puma
198
213
 
199
214
  @before_thread_exit.each do |b|
200
215
  begin
201
- b.call
216
+ b[:block].call
202
217
  rescue Exception => e
203
218
  STDERR.puts "WARNING before_thread_exit hook failed with exception (#{e.class}) #{e.message}"
204
219
  end
@@ -214,16 +229,27 @@ module Puma
214
229
 
215
230
  # we execute on idle hook when all threads are free
216
231
  return false unless @spawned == @waiting
217
-
218
- @out_of_band.each(&:call)
232
+ @out_of_band_running = true
233
+ @out_of_band.each { |b| b[:block].call }
219
234
  true
220
235
  rescue Exception => e
221
236
  STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
222
237
  true
238
+ ensure
239
+ @out_of_band_running = false
240
+ @out_of_band_condvar.broadcast
223
241
  end
224
242
 
225
243
  private :trigger_out_of_band_hook
226
244
 
245
+ def wait_while_out_of_band_running
246
+ return unless @out_of_band_running
247
+
248
+ with_mutex do
249
+ @out_of_band_condvar.wait(@mutex) while @out_of_band_running
250
+ end
251
+ end
252
+
227
253
  # @version 5.0.0
228
254
  def with_mutex(&block)
229
255
  @mutex.owned? ?
@@ -239,6 +265,8 @@ module Puma
239
265
  end
240
266
 
241
267
  @todo << work
268
+ t = @todo.size
269
+ @backlog_max = t if t > @backlog_max
242
270
 
243
271
  if @waiting < @todo.size and @spawned < @max
244
272
  spawn_thread
@@ -248,69 +276,6 @@ module Puma
248
276
  end
249
277
  end
250
278
 
251
- # This method is used by `Puma::Server` to let the server know when
252
- # the thread pool can pull more requests from the socket and
253
- # pass to the reactor.
254
- #
255
- # The general idea is that the thread pool can only work on a fixed
256
- # number of requests at the same time. If it is already processing that
257
- # number of requests then it is at capacity. If another Puma process has
258
- # spare capacity, then the request can be left on the socket so the other
259
- # worker can pick it up and process it.
260
- #
261
- # For example: if there are 5 threads, but only 4 working on
262
- # requests, this method will not wait and the `Puma::Server`
263
- # can pull a request right away.
264
- #
265
- # If there are 5 threads and all 5 of them are busy, then it will
266
- # pause here, and wait until the `not_full` condition variable is
267
- # signaled, usually this indicates that a request has been processed.
268
- #
269
- # It's important to note that even though the server might accept another
270
- # request, it might not be added to the `@todo` array right away.
271
- # For example if a slow client has only sent a header, but not a body
272
- # then the `@todo` array would stay the same size as the reactor works
273
- # to try to buffer the request. In that scenario the next call to this
274
- # method would not block and another request would be added into the reactor
275
- # by the server. This would continue until a fully buffered request
276
- # makes it through the reactor and can then be processed by the thread pool.
277
- def wait_until_not_full
278
- with_mutex do
279
- while true
280
- return if @shutdown
281
-
282
- # If we can still spin up new threads and there
283
- # is work queued that cannot be handled by waiting
284
- # threads, then accept more work until we would
285
- # spin up the max number of threads.
286
- return if busy_threads < @max
287
-
288
- @not_full.wait @mutex
289
- end
290
- end
291
- end
292
-
293
- # @version 5.0.0
294
- def wait_for_less_busy_worker(delay_s)
295
- return unless delay_s && delay_s > 0
296
-
297
- # Ruby MRI does GVL, this can result
298
- # in processing contention when multiple threads
299
- # (requests) are running concurrently
300
- return unless Puma.mri?
301
-
302
- with_mutex do
303
- return if @shutdown
304
-
305
- # do not delay, if we are not busy
306
- return unless busy_threads > 0
307
-
308
- # this will be signaled once a request finishes,
309
- # which can happen earlier than delay
310
- @not_full.wait @mutex, delay_s
311
- end
312
- end
313
-
314
279
  # If there are any free threads in the pool, tell one to go ahead
315
280
  # and exit. If +force+ is true, then a trim request is requested
316
281
  # even if all threads are being utilized.
data/lib/puma/util.rb CHANGED
@@ -10,13 +10,6 @@ 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 affects 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
-
20
13
  # Escapes and unescapes a URI escaped string with
21
14
  # +encoding+. +encoding+ will be the target encoding of the string
22
15
  # returned, and it defaults to UTF-8
data/lib/puma.rb CHANGED
@@ -75,4 +75,14 @@ module Puma
75
75
  def self.set_thread_name(name)
76
76
  Thread.current.name = "puma #{name}"
77
77
  end
78
+
79
+ # Shows deprecated warning for renamed methods.
80
+ # @example
81
+ # Puma.deprecate_method_change :on_booted, __callee__, __method__
82
+ #
83
+ def self.deprecate_method_change(method_old, method_caller, method_new)
84
+ if method_old == method_caller
85
+ warn "Use '#{method_new}', '#{method_caller}' is deprecated and will be removed in v8"
86
+ end
87
+ end
78
88
  end
@@ -32,7 +32,7 @@ module Puma
32
32
 
33
33
  @events = options[:events] || ::Puma::Events.new
34
34
 
35
- conf = ::Puma::Configuration.new(options, default_options.merge({events: @events})) do |user_config, file_config, default_config|
35
+ conf = ::Puma::Configuration.new(options, default_options.merge({ events: @events })) do |user_config, file_config, default_config|
36
36
  if options.delete(:Verbose)
37
37
  begin
38
38
  require 'rack/commonlogger' # Rack 1.x
@@ -72,7 +72,7 @@ module Puma
72
72
 
73
73
  log_writer = options.delete(:Silent) ? ::Puma::LogWriter.strings : ::Puma::LogWriter.stdio
74
74
 
75
- launcher = ::Puma::Launcher.new(conf, :log_writer => log_writer, events: @events)
75
+ launcher = ::Puma::Launcher.new(conf, log_writer: log_writer, events: @events)
76
76
 
77
77
  yield launcher if block_given?
78
78
  begin