puma 5.6.7 → 6.4.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 +269 -13
  3. data/README.md +78 -29
  4. data/bin/puma-wild +1 -1
  5. data/docs/compile_options.md +34 -0
  6. data/docs/fork_worker.md +1 -3
  7. data/docs/kubernetes.md +12 -0
  8. data/docs/nginx.md +1 -1
  9. data/docs/systemd.md +1 -2
  10. data/docs/testing_benchmarks_local_files.md +150 -0
  11. data/docs/testing_test_rackup_ci_files.md +36 -0
  12. data/ext/puma_http11/extconf.rb +11 -8
  13. data/ext/puma_http11/http11_parser.c +1 -1
  14. data/ext/puma_http11/http11_parser.h +1 -1
  15. data/ext/puma_http11/http11_parser.java.rl +2 -2
  16. data/ext/puma_http11/http11_parser.rl +2 -2
  17. data/ext/puma_http11/http11_parser_common.rl +2 -2
  18. data/ext/puma_http11/mini_ssl.c +122 -18
  19. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  20. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  21. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +156 -53
  22. data/ext/puma_http11/puma_http11.c +17 -9
  23. data/lib/puma/app/status.rb +4 -4
  24. data/lib/puma/binder.rb +50 -53
  25. data/lib/puma/cli.rb +16 -18
  26. data/lib/puma/client.rb +59 -19
  27. data/lib/puma/cluster/worker.rb +18 -11
  28. data/lib/puma/cluster/worker_handle.rb +4 -1
  29. data/lib/puma/cluster.rb +33 -30
  30. data/lib/puma/commonlogger.rb +21 -14
  31. data/lib/puma/configuration.rb +78 -58
  32. data/lib/puma/const.rb +129 -92
  33. data/lib/puma/control_cli.rb +15 -11
  34. data/lib/puma/detect.rb +4 -0
  35. data/lib/puma/dsl.rb +237 -56
  36. data/lib/puma/error_logger.rb +18 -9
  37. data/lib/puma/events.rb +6 -126
  38. data/lib/puma/io_buffer.rb +39 -4
  39. data/lib/puma/jruby_restart.rb +2 -1
  40. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  41. data/lib/puma/launcher.rb +102 -175
  42. data/lib/puma/log_writer.rb +147 -0
  43. data/lib/puma/minissl/context_builder.rb +24 -12
  44. data/lib/puma/minissl.rb +99 -11
  45. data/lib/puma/plugin/systemd.rb +90 -0
  46. data/lib/puma/plugin/tmp_restart.rb +1 -1
  47. data/lib/puma/rack/builder.rb +6 -6
  48. data/lib/puma/rack/urlmap.rb +1 -1
  49. data/lib/puma/rack_default.rb +19 -4
  50. data/lib/puma/reactor.rb +19 -10
  51. data/lib/puma/request.rb +365 -170
  52. data/lib/puma/runner.rb +56 -20
  53. data/lib/puma/sd_notify.rb +149 -0
  54. data/lib/puma/server.rb +116 -89
  55. data/lib/puma/single.rb +13 -11
  56. data/lib/puma/state_file.rb +1 -4
  57. data/lib/puma/thread_pool.rb +57 -19
  58. data/lib/puma/util.rb +0 -11
  59. data/lib/puma.rb +9 -10
  60. data/lib/rack/handler/puma.rb +113 -86
  61. metadata +9 -5
  62. data/lib/puma/queue_close.rb +0 -26
  63. data/lib/puma/systemd.rb +0 -46
  64. data/lib/rack/version_restriction.rb +0 -15
data/lib/puma/server.rb CHANGED
@@ -2,20 +2,19 @@
2
2
 
3
3
  require 'stringio'
4
4
 
5
- require 'puma/thread_pool'
6
- require 'puma/const'
7
- require 'puma/events'
8
- require 'puma/null_io'
9
- require 'puma/reactor'
10
- require 'puma/client'
11
- require 'puma/binder'
12
- require 'puma/util'
13
- require 'puma/io_buffer'
14
- require 'puma/request'
5
+ require_relative 'thread_pool'
6
+ require_relative 'const'
7
+ require_relative 'log_writer'
8
+ require_relative 'events'
9
+ require_relative 'null_io'
10
+ require_relative 'reactor'
11
+ require_relative 'client'
12
+ require_relative 'binder'
13
+ require_relative 'util'
14
+ require_relative 'request'
15
15
 
16
16
  require 'socket'
17
- require 'io/wait'
18
- require 'forwardable'
17
+ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
19
18
 
20
19
  module Puma
21
20
 
@@ -30,39 +29,30 @@ module Puma
30
29
  #
31
30
  # Each `Puma::Server` will have one reactor and one thread pool.
32
31
  class Server
33
-
34
32
  include Puma::Const
35
33
  include Request
36
- extend Forwardable
37
34
 
38
35
  attr_reader :thread
36
+ attr_reader :log_writer
39
37
  attr_reader :events
40
38
  attr_reader :min_threads, :max_threads # for #stats
41
39
  attr_reader :requests_count # @version 5.0.0
42
- attr_reader :log_writer # to help with backports
43
40
 
44
41
  # @todo the following may be deprecated in the future
45
42
  attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
46
43
  :leak_stack_on_error,
47
44
  :persistent_timeout, :reaping_time
48
45
 
49
- # @deprecated v6.0.0
50
- attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
51
- :leak_stack_on_error, :min_threads, :max_threads,
52
- :persistent_timeout, :reaping_time
53
-
54
46
  attr_accessor :app
55
47
  attr_accessor :binder
56
48
 
57
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
58
- :add_unix_listener, :connected_ports
59
-
60
- ThreadLocalKey = :puma_server
49
+ THREAD_LOCAL_KEY = :puma_server
61
50
 
62
51
  # Create a server for the rack app +app+.
63
52
  #
64
- # +events+ is an object which will be called when certain error events occur
65
- # to be handled. See Puma::Events for the list of current methods to implement.
53
+ # +log_writer+ is a Puma::LogWriter object used to log info and error messages.
54
+ #
55
+ # +events+ is a Puma::Events object used to notify application status events.
66
56
  #
67
57
  # Server#run returns a thread that you can join on to wait for the server
68
58
  # to do its work.
@@ -71,35 +61,54 @@ module Puma
71
61
  # and have default values set via +fetch+. Normally the values are set via
72
62
  # `::Puma::Configuration.puma_default_options`.
73
63
  #
74
- def initialize(app, events=Events.stdio, options={})
64
+ # @note The `events` parameter is set to nil, and set to `Events.new` in code.
65
+ # Often `options` needs to be passed, but `events` does not. Using nil allows
66
+ # calling code to not require events.rb.
67
+ #
68
+ def initialize(app, events = nil, options = {})
75
69
  @app = app
76
- @events = events
77
- @log_writer = events
70
+ @events = events || Events.new
78
71
 
79
72
  @check, @notify = nil
80
73
  @status = :stop
81
74
 
82
- @auto_trim_time = 30
83
- @reaping_time = 1
84
-
85
75
  @thread = nil
86
76
  @thread_pool = nil
87
77
 
88
- @options = options
78
+ @options = if options.is_a?(UserFileDefaultOptions)
79
+ options
80
+ else
81
+ UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
82
+ end
89
83
 
90
- @early_hints = options.fetch :early_hints, nil
91
- @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
92
- @min_threads = options.fetch :min_threads, 0
93
- @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
94
- @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
95
- @queue_requests = options.fetch :queue_requests, true
96
- @max_fast_inline = options.fetch :max_fast_inline, MAX_FAST_INLINE
97
- @io_selector_backend = options.fetch :io_selector_backend, :auto
84
+ @log_writer = @options.fetch :log_writer, LogWriter.stdio
85
+ @early_hints = @options[:early_hints]
86
+ @first_data_timeout = @options[:first_data_timeout]
87
+ @persistent_timeout = @options[:persistent_timeout]
88
+ @idle_timeout = @options[:idle_timeout]
89
+ @min_threads = @options[:min_threads]
90
+ @max_threads = @options[:max_threads]
91
+ @queue_requests = @options[:queue_requests]
92
+ @max_fast_inline = @options[:max_fast_inline]
93
+ @io_selector_backend = @options[:io_selector_backend]
94
+ @http_content_length_limit = @options[:http_content_length_limit]
95
+
96
+ # make this a hash, since we prefer `key?` over `include?`
97
+ @supported_http_methods =
98
+ if @options[:supported_http_methods] == :any
99
+ :any
100
+ else
101
+ if (ary = @options[:supported_http_methods])
102
+ ary
103
+ else
104
+ SUPPORTED_HTTP_METHODS
105
+ end.sort.product([nil]).to_h.freeze
106
+ end
98
107
 
99
108
  temp = !!(@options[:environment] =~ /\A(development|test)\z/)
100
109
  @leak_stack_on_error = @options[:environment] ? temp : true
101
110
 
102
- @binder = Binder.new(events)
111
+ @binder = Binder.new(log_writer)
103
112
 
104
113
  ENV['RACK_ENV'] ||= "development"
105
114
 
@@ -117,7 +126,7 @@ module Puma
117
126
  class << self
118
127
  # @!attribute [r] current
119
128
  def current
120
- Thread.current[ThreadLocalKey]
129
+ Thread.current[THREAD_LOCAL_KEY]
121
130
  end
122
131
 
123
132
  # :nodoc:
@@ -195,12 +204,12 @@ module Puma
195
204
 
196
205
  # @!attribute [r] backlog
197
206
  def backlog
198
- @thread_pool and @thread_pool.backlog
207
+ @thread_pool&.backlog
199
208
  end
200
209
 
201
210
  # @!attribute [r] running
202
211
  def running
203
- @thread_pool and @thread_pool.spawned
212
+ @thread_pool&.spawned
204
213
  end
205
214
 
206
215
 
@@ -213,7 +222,7 @@ module Puma
213
222
  # value would be 4 until it finishes processing.
214
223
  # @!attribute [r] pool_capacity
215
224
  def pool_capacity
216
- @thread_pool and @thread_pool.pool_capacity
225
+ @thread_pool&.pool_capacity
217
226
  end
218
227
 
219
228
  # Runs the server.
@@ -229,29 +238,16 @@ module Puma
229
238
 
230
239
  @status = :run
231
240
 
232
- @thread_pool = ThreadPool.new(
233
- thread_name,
234
- @min_threads,
235
- @max_threads,
236
- ::Puma::IOBuffer,
237
- &method(:process_client)
238
- )
239
-
240
- @thread_pool.out_of_band_hook = @options[:out_of_band]
241
- @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
241
+ @thread_pool = ThreadPool.new(thread_name, @options) { |client| process_client client }
242
242
 
243
243
  if @queue_requests
244
- @reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
244
+ @reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
245
245
  @reactor.run
246
246
  end
247
247
 
248
- if @reaping_time
249
- @thread_pool.auto_reap!(@reaping_time)
250
- end
251
248
 
252
- if @auto_trim_time
253
- @thread_pool.auto_trim!(@auto_trim_time)
254
- end
249
+ @thread_pool.auto_reap! if @options[:reaping_time]
250
+ @thread_pool.auto_trim! if @options[:auto_trim_time]
255
251
 
256
252
  @check, @notify = Puma::Util.pipe unless @notify
257
253
 
@@ -330,8 +326,12 @@ module Puma
330
326
 
331
327
  while @status == :run || (drain && shutting_down?)
332
328
  begin
333
- ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
334
- break unless ios
329
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
330
+ unless ios
331
+ @status = :stop unless shutting_down?
332
+ break
333
+ end
334
+
335
335
  ios.first.each do |sock|
336
336
  if sock == check
337
337
  break if handle_check
@@ -347,6 +347,7 @@ module Puma
347
347
  drain += 1 if shutting_down?
348
348
  pool << Client.new(io, @binder.env(sock)).tap { |c|
349
349
  c.listener = sock
350
+ c.http_content_length_limit = @http_content_length_limit
350
351
  c.send(addr_send_name, addr_value) if addr_value
351
352
  }
352
353
  end
@@ -355,11 +356,11 @@ module Puma
355
356
  # In the case that any of the sockets are unexpectedly close.
356
357
  raise
357
358
  rescue StandardError => e
358
- @events.unknown_error e, nil, "Listen loop"
359
+ @log_writer.unknown_error e, nil, "Listen loop"
359
360
  end
360
361
  end
361
362
 
362
- @events.debug "Drained #{drain} additional connections." if drain
363
+ @log_writer.debug "Drained #{drain} additional connections." if drain
363
364
  @events.fire :state, @status
364
365
 
365
366
  if queue_requests
@@ -368,14 +369,13 @@ module Puma
368
369
  end
369
370
  graceful_shutdown if @status == :stop || @status == :restart
370
371
  rescue Exception => e
371
- @events.unknown_error e, nil, "Exception handling servers"
372
+ @log_writer.unknown_error e, nil, "Exception handling servers"
372
373
  ensure
373
- # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
374
374
  # Errno::EBADF is infrequently raised
375
375
  [@check, @notify].each do |io|
376
376
  begin
377
377
  io.close unless io.closed?
378
- rescue Errno::EBADF, RuntimeError
378
+ rescue Errno::EBADF
379
379
  end
380
380
  end
381
381
  @notify = nil
@@ -414,9 +414,9 @@ module Puma
414
414
  # returning.
415
415
  #
416
416
  # Return true if one or more requests were processed.
417
- def process_client(client, buffer)
417
+ def process_client(client)
418
418
  # Advertise this server into the thread
419
- Thread.current[ThreadLocalKey] = self
419
+ Thread.current[THREAD_LOCAL_KEY] = self
420
420
 
421
421
  clean_thread_locals = @options[:clean_thread_locals]
422
422
  close_socket = true
@@ -440,15 +440,13 @@ module Puma
440
440
 
441
441
  while true
442
442
  @requests_count += 1
443
- case handle_request(client, buffer, requests + 1)
443
+ case handle_request(client, requests + 1)
444
444
  when false
445
445
  break
446
446
  when :async
447
447
  close_socket = false
448
448
  break
449
449
  when true
450
- buffer.reset
451
-
452
450
  ThreadPool.clean_thread_locals if clean_thread_locals
453
451
 
454
452
  requests += 1
@@ -478,11 +476,11 @@ module Puma
478
476
  end
479
477
  true
480
478
  rescue StandardError => e
481
- client_error(e, client)
479
+ client_error(e, client, requests)
482
480
  # The ensure tries to close +client+ down
483
481
  requests > 0
484
482
  ensure
485
- buffer.reset
483
+ client.io_buffer.reset
486
484
 
487
485
  begin
488
486
  client.close if close_socket
@@ -490,7 +488,7 @@ module Puma
490
488
  Puma::Util.purge_interrupt_queue
491
489
  # Already closed
492
490
  rescue StandardError => e
493
- @events.unknown_error e, nil, "Client"
491
+ @log_writer.unknown_error e, nil, "Client"
494
492
  end
495
493
  end
496
494
  end
@@ -506,23 +504,23 @@ module Puma
506
504
  # :nocov:
507
505
 
508
506
  # Handle various error types thrown by Client I/O operations.
509
- def client_error(e, client)
507
+ def client_error(e, client, requests = 1)
510
508
  # Swallow, do not log
511
509
  return if [ConnectionError, EOFError].include?(e.class)
512
510
 
513
- lowlevel_error(e, client.env)
514
511
  case e
515
512
  when MiniSSL::SSLError
516
- @events.ssl_error e, client.io
513
+ lowlevel_error(e, client.env)
514
+ @log_writer.ssl_error e, client.io
517
515
  when HttpParserError
518
- client.write_error(400)
519
- @events.parse_error e, client
516
+ response_to_error(client, requests, e, 400)
517
+ @log_writer.parse_error e, client
520
518
  when HttpParserError501
521
- client.write_error(501)
522
- @events.parse_error e, client
519
+ response_to_error(client, requests, e, 501)
520
+ @log_writer.parse_error e, client
523
521
  else
524
- client.write_error(500)
525
- @events.unknown_error e, nil, "Read"
522
+ response_to_error(client, requests, e, 500)
523
+ @log_writer.unknown_error e, nil, "Read"
526
524
  end
527
525
  end
528
526
 
@@ -543,10 +541,17 @@ module Puma
543
541
  backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
544
542
  [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
545
543
  else
546
- [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
544
+ [status, {}, [""]]
547
545
  end
548
546
  end
549
547
 
548
+ def response_to_error(client, requests, err, status_code)
549
+ status, headers, res_body = lowlevel_error(err, client.env, status_code)
550
+ prepare_response(status, headers, res_body, requests, client)
551
+ client.write_error(status_code)
552
+ end
553
+ private :response_to_error
554
+
550
555
  # Wait for all outstanding requests to finish.
551
556
  #
552
557
  def graceful_shutdown
@@ -580,7 +585,7 @@ module Puma
580
585
 
581
586
  def notify_safely(message)
582
587
  @notify << message
583
- rescue IOError, NoMethodError, Errno::EPIPE
588
+ rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
584
589
  # The server, in another thread, is shutting down
585
590
  Puma::Util.purge_interrupt_queue
586
591
  rescue RuntimeError => e
@@ -625,5 +630,27 @@ module Puma
625
630
  def stats
626
631
  STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
627
632
  end
633
+
634
+ # below are 'delegations' to binder
635
+ # remove in Puma 7?
636
+
637
+
638
+ def add_tcp_listener(host, port, optimize_for_latency = true, backlog = 1024)
639
+ @binder.add_tcp_listener host, port, optimize_for_latency, backlog
640
+ end
641
+
642
+ def add_ssl_listener(host, port, ctx, optimize_for_latency = true,
643
+ backlog = 1024)
644
+ @binder.add_ssl_listener host, port, ctx, optimize_for_latency, backlog
645
+ end
646
+
647
+ def add_unix_listener(path, umask = nil, mode = nil, backlog = 1024)
648
+ @binder.add_unix_listener path, umask, mode, backlog
649
+ end
650
+
651
+ # @!attribute [r] connected_ports
652
+ def connected_ports
653
+ @binder.connected_ports
654
+ end
628
655
  end
629
656
  end
data/lib/puma/single.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/runner'
4
- require 'puma/detect'
5
- require 'puma/plugin'
3
+ require_relative 'runner'
4
+ require_relative 'detect'
5
+ require_relative 'plugin'
6
6
 
7
7
  module Puma
8
8
  # This class is instantiated by the `Puma::Launcher` and used
@@ -16,26 +16,26 @@ module Puma
16
16
  # @!attribute [r] stats
17
17
  def stats
18
18
  {
19
- started_at: @started_at.utc.iso8601
20
- }.merge(@server.stats)
19
+ started_at: utc_iso8601(@started_at)
20
+ }.merge(@server.stats).merge(super)
21
21
  end
22
22
 
23
23
  def restart
24
- @server.begin_restart
24
+ @server&.begin_restart
25
25
  end
26
26
 
27
27
  def stop
28
- @server.stop(false) if @server
28
+ @server&.stop false
29
29
  end
30
30
 
31
31
  def halt
32
- @server.halt
32
+ @server&.halt
33
33
  end
34
34
 
35
35
  def stop_blocked
36
36
  log "- Gracefully stopping, waiting for requests to finish"
37
- @control.stop(true) if @control
38
- @server.stop(true) if @server
37
+ @control&.stop true
38
+ @server&.stop true
39
39
  end
40
40
 
41
41
  def run
@@ -55,7 +55,9 @@ module Puma
55
55
  log "Use Ctrl-C to stop"
56
56
  redirect_io
57
57
 
58
- @launcher.events.fire_on_booted!
58
+ @events.fire_on_booted!
59
+
60
+ debug_loaded_extensions("Loaded Extensions:") if @log_writer.debug?
59
61
 
60
62
  begin
61
63
  server_thread.join
@@ -15,15 +15,12 @@ module Puma
15
15
 
16
16
  ALLOWED_FIELDS = %w!control_url control_auth_token pid running_from!
17
17
 
18
- # @deprecated 6.0.0
19
- FIELDS = ALLOWED_FIELDS
20
-
21
18
  def initialize
22
19
  @options = {}
23
20
  end
24
21
 
25
22
  def save(path, permission = nil)
26
- contents = "---\n".dup
23
+ contents = +"---\n"
27
24
  @options.each do |k,v|
28
25
  next unless ALLOWED_FIELDS.include? k
29
26
  case v
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'thread'
4
4
 
5
+ require_relative 'io_buffer'
6
+
5
7
  module Puma
6
8
  # Internal Docs for A simple thread pool management object.
7
9
  #
@@ -29,7 +31,7 @@ module Puma
29
31
  # The block passed is the work that will be performed in each
30
32
  # thread.
31
33
  #
32
- def initialize(name, min, max, *extra, &block)
34
+ def initialize(name, options = {}, &block)
33
35
  @not_empty = ConditionVariable.new
34
36
  @not_full = ConditionVariable.new
35
37
  @mutex = Mutex.new
@@ -40,10 +42,19 @@ module Puma
40
42
  @waiting = 0
41
43
 
42
44
  @name = name
43
- @min = Integer(min)
44
- @max = Integer(max)
45
+ @min = Integer(options[:min_threads])
46
+ @max = Integer(options[:max_threads])
47
+ # Not an 'exposed' option, options[:pool_shutdown_grace_time] is used in CI
48
+ # to shorten @shutdown_grace_time from SHUTDOWN_GRACE_TIME. Parallel CI
49
+ # makes stubbing constants difficult.
50
+ @shutdown_grace_time = Float(options[:pool_shutdown_grace_time] || SHUTDOWN_GRACE_TIME)
45
51
  @block = block
46
- @extra = extra
52
+ @out_of_band = options[:out_of_band]
53
+ @clean_thread_locals = options[:clean_thread_locals]
54
+ @before_thread_start = options[:before_thread_start]
55
+ @before_thread_exit = options[:before_thread_exit]
56
+ @reaping_time = options[:reaping_time]
57
+ @auto_trim_time = options[:auto_trim_time]
47
58
 
48
59
  @shutdown = false
49
60
 
@@ -62,14 +73,11 @@ module Puma
62
73
  end
63
74
  end
64
75
 
65
- @clean_thread_locals = false
66
76
  @force_shutdown = false
67
77
  @shutdown_mutex = Mutex.new
68
78
  end
69
79
 
70
80
  attr_reader :spawned, :trim_requested, :waiting
71
- attr_accessor :clean_thread_locals
72
- attr_accessor :out_of_band_hook # @version 5.0.0
73
81
 
74
82
  def self.clean_thread_locals
75
83
  Thread.current.keys.each do |key| # rubocop: disable Style/HashEachMethods
@@ -101,6 +109,7 @@ module Puma
101
109
  def spawn_thread
102
110
  @spawned += 1
103
111
 
112
+ trigger_before_thread_start_hooks
104
113
  th = Thread.new(@spawned) do |spawned|
105
114
  Puma.set_thread_name '%s tp %03i' % [@name, spawned]
106
115
  todo = @todo
@@ -109,8 +118,6 @@ module Puma
109
118
  not_empty = @not_empty
110
119
  not_full = @not_full
111
120
 
112
- extra = @extra.map { |i| i.new }
113
-
114
121
  while true
115
122
  work = nil
116
123
 
@@ -121,6 +128,7 @@ module Puma
121
128
  @spawned -= 1
122
129
  @workers.delete th
123
130
  not_full.signal
131
+ trigger_before_thread_exit_hooks
124
132
  Thread.exit
125
133
  end
126
134
 
@@ -144,7 +152,7 @@ module Puma
144
152
  end
145
153
 
146
154
  begin
147
- @out_of_band_pending = true if block.call(work, *extra)
155
+ @out_of_band_pending = true if block.call(work)
148
156
  rescue Exception => e
149
157
  STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
150
158
  end
@@ -158,14 +166,44 @@ module Puma
158
166
 
159
167
  private :spawn_thread
160
168
 
169
+ def trigger_before_thread_start_hooks
170
+ return unless @before_thread_start&.any?
171
+
172
+ @before_thread_start.each do |b|
173
+ begin
174
+ b.call
175
+ rescue Exception => e
176
+ STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
177
+ end
178
+ end
179
+ nil
180
+ end
181
+
182
+ private :trigger_before_thread_start_hooks
183
+
184
+ def trigger_before_thread_exit_hooks
185
+ return unless @before_thread_exit&.any?
186
+
187
+ @before_thread_exit.each do |b|
188
+ begin
189
+ b.call
190
+ rescue Exception => e
191
+ STDERR.puts "WARNING before_thread_exit hook failed with exception (#{e.class}) #{e.message}"
192
+ end
193
+ end
194
+ nil
195
+ end
196
+
197
+ private :trigger_before_thread_exit_hooks
198
+
161
199
  # @version 5.0.0
162
200
  def trigger_out_of_band_hook
163
- return false unless out_of_band_hook && out_of_band_hook.any?
201
+ return false unless @out_of_band&.any?
164
202
 
165
203
  # we execute on idle hook when all threads are free
166
204
  return false unless @spawned == @waiting
167
205
 
168
- out_of_band_hook.each(&:call)
206
+ @out_of_band.each(&:call)
169
207
  true
170
208
  rescue Exception => e
171
209
  STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
@@ -319,12 +357,12 @@ module Puma
319
357
  end
320
358
  end
321
359
 
322
- def auto_trim!(timeout=30)
360
+ def auto_trim!(timeout=@auto_trim_time)
323
361
  @auto_trim = Automaton.new(self, timeout, "#{@name} threadpool trimmer", :trim)
324
362
  @auto_trim.start!
325
363
  end
326
364
 
327
- def auto_reap!(timeout=5)
365
+ def auto_reap!(timeout=@reaping_time)
328
366
  @reaper = Automaton.new(self, timeout, "#{@name} threadpool reaper", :reap)
329
367
  @reaper.start!
330
368
  end
@@ -344,8 +382,8 @@ module Puma
344
382
 
345
383
  # Tell all threads in the pool to exit and wait for them to finish.
346
384
  # Wait +timeout+ seconds then raise +ForceShutdown+ in remaining threads.
347
- # Next, wait an extra +grace+ seconds then force-kill remaining threads.
348
- # Finally, wait +kill_grace+ seconds for remaining threads to exit.
385
+ # Next, wait an extra +@shutdown_grace_time+ seconds then force-kill remaining
386
+ # threads. Finally, wait 1 second for remaining threads to exit.
349
387
  #
350
388
  def shutdown(timeout=-1)
351
389
  threads = with_mutex do
@@ -354,8 +392,8 @@ module Puma
354
392
  @not_empty.broadcast
355
393
  @not_full.broadcast
356
394
 
357
- @auto_trim.stop if @auto_trim
358
- @reaper.stop if @reaper
395
+ @auto_trim&.stop
396
+ @reaper&.stop
359
397
  # dup workers so that we join them all safely
360
398
  @workers.dup
361
399
  end
@@ -382,7 +420,7 @@ module Puma
382
420
  t.raise ForceShutdown if t[:with_force_shutdown]
383
421
  end
384
422
  end
385
- join.call(SHUTDOWN_GRACE_TIME)
423
+ join.call(@shutdown_grace_time)
386
424
 
387
425
  # If threads are _still_ running, forcefully kill them and wait to finish.
388
426
  threads.each(&:kill)
data/lib/puma/util.rb CHANGED
@@ -39,17 +39,6 @@ module Puma
39
39
  end
40
40
  module_function :unescape, :escape
41
41
 
42
- # @version 5.0.0
43
- def nakayoshi_gc(events)
44
- events.log "! Promoting existing objects to old generation..."
45
- 4.times { GC.start(full_mark: false) }
46
- if GC.respond_to?(:compact)
47
- events.log "! Compacting..."
48
- GC.compact
49
- end
50
- events.log "! Friendly fork preparation complete."
51
- end
52
-
53
42
  DEFAULT_SEP = /[&;] */n
54
43
 
55
44
  # Stolen from Mongrel, with some small modifications: