puma 5.6.8 → 6.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +332 -16
  3. data/README.md +79 -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/restart.md +1 -0
  10. data/docs/systemd.md +3 -6
  11. data/docs/testing_benchmarks_local_files.md +150 -0
  12. data/docs/testing_test_rackup_ci_files.md +36 -0
  13. data/ext/puma_http11/extconf.rb +16 -9
  14. data/ext/puma_http11/http11_parser.c +1 -1
  15. data/ext/puma_http11/http11_parser.h +1 -1
  16. data/ext/puma_http11/http11_parser.java.rl +2 -2
  17. data/ext/puma_http11/http11_parser.rl +2 -2
  18. data/ext/puma_http11/http11_parser_common.rl +2 -2
  19. data/ext/puma_http11/mini_ssl.c +127 -19
  20. data/ext/puma_http11/org/jruby/puma/Http11.java +5 -3
  21. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  22. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +157 -53
  23. data/ext/puma_http11/puma_http11.c +17 -9
  24. data/lib/puma/app/status.rb +4 -4
  25. data/lib/puma/binder.rb +50 -53
  26. data/lib/puma/cli.rb +16 -18
  27. data/lib/puma/client.rb +59 -19
  28. data/lib/puma/cluster/worker.rb +18 -11
  29. data/lib/puma/cluster/worker_handle.rb +4 -1
  30. data/lib/puma/cluster.rb +102 -40
  31. data/lib/puma/commonlogger.rb +21 -14
  32. data/lib/puma/configuration.rb +77 -59
  33. data/lib/puma/const.rb +137 -92
  34. data/lib/puma/control_cli.rb +15 -11
  35. data/lib/puma/detect.rb +7 -4
  36. data/lib/puma/dsl.rb +250 -56
  37. data/lib/puma/error_logger.rb +18 -9
  38. data/lib/puma/events.rb +6 -126
  39. data/lib/puma/io_buffer.rb +39 -4
  40. data/lib/puma/jruby_restart.rb +2 -1
  41. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  42. data/lib/puma/launcher.rb +102 -175
  43. data/lib/puma/log_writer.rb +147 -0
  44. data/lib/puma/minissl/context_builder.rb +26 -12
  45. data/lib/puma/minissl.rb +104 -11
  46. data/lib/puma/null_io.rb +16 -2
  47. data/lib/puma/plugin/systemd.rb +90 -0
  48. data/lib/puma/plugin/tmp_restart.rb +1 -1
  49. data/lib/puma/rack/builder.rb +6 -6
  50. data/lib/puma/rack/urlmap.rb +1 -1
  51. data/lib/puma/rack_default.rb +19 -4
  52. data/lib/puma/reactor.rb +19 -10
  53. data/lib/puma/request.rb +380 -172
  54. data/lib/puma/runner.rb +56 -20
  55. data/lib/puma/sd_notify.rb +149 -0
  56. data/lib/puma/server.rb +137 -89
  57. data/lib/puma/single.rb +13 -11
  58. data/lib/puma/state_file.rb +3 -6
  59. data/lib/puma/thread_pool.rb +57 -19
  60. data/lib/puma/util.rb +0 -11
  61. data/lib/puma.rb +9 -10
  62. data/lib/rack/handler/puma.rb +113 -86
  63. data/tools/Dockerfile +2 -2
  64. metadata +11 -7
  65. data/lib/puma/queue_close.rb +0 -26
  66. data/lib/puma/systemd.rb +0 -46
  67. 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,56 @@ 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
+ @clustered = (@options.fetch :workers, 0) > 0
85
+ @worker_write = @options[:worker_write]
86
+ @log_writer = @options.fetch :log_writer, LogWriter.stdio
87
+ @early_hints = @options[:early_hints]
88
+ @first_data_timeout = @options[:first_data_timeout]
89
+ @persistent_timeout = @options[:persistent_timeout]
90
+ @idle_timeout = @options[:idle_timeout]
91
+ @min_threads = @options[:min_threads]
92
+ @max_threads = @options[:max_threads]
93
+ @queue_requests = @options[:queue_requests]
94
+ @max_fast_inline = @options[:max_fast_inline]
95
+ @io_selector_backend = @options[:io_selector_backend]
96
+ @http_content_length_limit = @options[:http_content_length_limit]
97
+
98
+ # make this a hash, since we prefer `key?` over `include?`
99
+ @supported_http_methods =
100
+ if @options[:supported_http_methods] == :any
101
+ :any
102
+ else
103
+ if (ary = @options[:supported_http_methods])
104
+ ary
105
+ else
106
+ SUPPORTED_HTTP_METHODS
107
+ end.sort.product([nil]).to_h.freeze
108
+ end
98
109
 
99
110
  temp = !!(@options[:environment] =~ /\A(development|test)\z/)
100
111
  @leak_stack_on_error = @options[:environment] ? temp : true
101
112
 
102
- @binder = Binder.new(events)
113
+ @binder = Binder.new(log_writer)
103
114
 
104
115
  ENV['RACK_ENV'] ||= "development"
105
116
 
@@ -108,6 +119,8 @@ module Puma
108
119
  @precheck_closing = true
109
120
 
110
121
  @requests_count = 0
122
+
123
+ @idle_timeout_reached = false
111
124
  end
112
125
 
113
126
  def inherit_binder(bind)
@@ -117,7 +130,7 @@ module Puma
117
130
  class << self
118
131
  # @!attribute [r] current
119
132
  def current
120
- Thread.current[ThreadLocalKey]
133
+ Thread.current[THREAD_LOCAL_KEY]
121
134
  end
122
135
 
123
136
  # :nodoc:
@@ -195,12 +208,12 @@ module Puma
195
208
 
196
209
  # @!attribute [r] backlog
197
210
  def backlog
198
- @thread_pool and @thread_pool.backlog
211
+ @thread_pool&.backlog
199
212
  end
200
213
 
201
214
  # @!attribute [r] running
202
215
  def running
203
- @thread_pool and @thread_pool.spawned
216
+ @thread_pool&.spawned
204
217
  end
205
218
 
206
219
 
@@ -213,7 +226,7 @@ module Puma
213
226
  # value would be 4 until it finishes processing.
214
227
  # @!attribute [r] pool_capacity
215
228
  def pool_capacity
216
- @thread_pool and @thread_pool.pool_capacity
229
+ @thread_pool&.pool_capacity
217
230
  end
218
231
 
219
232
  # Runs the server.
@@ -229,29 +242,16 @@ module Puma
229
242
 
230
243
  @status = :run
231
244
 
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]
245
+ @thread_pool = ThreadPool.new(thread_name, @options) { |client| process_client client }
242
246
 
243
247
  if @queue_requests
244
- @reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
248
+ @reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
245
249
  @reactor.run
246
250
  end
247
251
 
248
- if @reaping_time
249
- @thread_pool.auto_reap!(@reaping_time)
250
- end
251
252
 
252
- if @auto_trim_time
253
- @thread_pool.auto_trim!(@auto_trim_time)
254
- end
253
+ @thread_pool.auto_reap! if @options[:reaping_time]
254
+ @thread_pool.auto_trim! if @options[:auto_trim_time]
255
255
 
256
256
  @check, @notify = Puma::Util.pipe unless @notify
257
257
 
@@ -330,8 +330,28 @@ module Puma
330
330
 
331
331
  while @status == :run || (drain && shutting_down?)
332
332
  begin
333
- ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
334
- break unless ios
333
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
334
+ unless ios
335
+ unless shutting_down?
336
+ @idle_timeout_reached = true
337
+
338
+ if @clustered
339
+ @worker_write << "i#{Process.pid}\n" rescue nil
340
+ next
341
+ else
342
+ @log_writer.log "- Idle timeout reached"
343
+ @status = :stop
344
+ end
345
+ end
346
+
347
+ break
348
+ end
349
+
350
+ if @idle_timeout_reached && @clustered
351
+ @idle_timeout_reached = false
352
+ @worker_write << "i#{Process.pid}\n" rescue nil
353
+ end
354
+
335
355
  ios.first.each do |sock|
336
356
  if sock == check
337
357
  break if handle_check
@@ -347,6 +367,7 @@ module Puma
347
367
  drain += 1 if shutting_down?
348
368
  pool << Client.new(io, @binder.env(sock)).tap { |c|
349
369
  c.listener = sock
370
+ c.http_content_length_limit = @http_content_length_limit
350
371
  c.send(addr_send_name, addr_value) if addr_value
351
372
  }
352
373
  end
@@ -355,27 +376,27 @@ module Puma
355
376
  # In the case that any of the sockets are unexpectedly close.
356
377
  raise
357
378
  rescue StandardError => e
358
- @events.unknown_error e, nil, "Listen loop"
379
+ @log_writer.unknown_error e, nil, "Listen loop"
359
380
  end
360
381
  end
361
382
 
362
- @events.debug "Drained #{drain} additional connections." if drain
383
+ @log_writer.debug "Drained #{drain} additional connections." if drain
363
384
  @events.fire :state, @status
364
385
 
365
386
  if queue_requests
366
387
  @queue_requests = false
367
388
  @reactor.shutdown
368
389
  end
390
+
369
391
  graceful_shutdown if @status == :stop || @status == :restart
370
392
  rescue Exception => e
371
- @events.unknown_error e, nil, "Exception handling servers"
393
+ @log_writer.unknown_error e, nil, "Exception handling servers"
372
394
  ensure
373
- # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
374
395
  # Errno::EBADF is infrequently raised
375
396
  [@check, @notify].each do |io|
376
397
  begin
377
398
  io.close unless io.closed?
378
- rescue Errno::EBADF, RuntimeError
399
+ rescue Errno::EBADF
379
400
  end
380
401
  end
381
402
  @notify = nil
@@ -414,9 +435,9 @@ module Puma
414
435
  # returning.
415
436
  #
416
437
  # Return true if one or more requests were processed.
417
- def process_client(client, buffer)
438
+ def process_client(client)
418
439
  # Advertise this server into the thread
419
- Thread.current[ThreadLocalKey] = self
440
+ Thread.current[THREAD_LOCAL_KEY] = self
420
441
 
421
442
  clean_thread_locals = @options[:clean_thread_locals]
422
443
  close_socket = true
@@ -440,15 +461,13 @@ module Puma
440
461
 
441
462
  while true
442
463
  @requests_count += 1
443
- case handle_request(client, buffer, requests + 1)
464
+ case handle_request(client, requests + 1)
444
465
  when false
445
466
  break
446
467
  when :async
447
468
  close_socket = false
448
469
  break
449
470
  when true
450
- buffer.reset
451
-
452
471
  ThreadPool.clean_thread_locals if clean_thread_locals
453
472
 
454
473
  requests += 1
@@ -478,11 +497,11 @@ module Puma
478
497
  end
479
498
  true
480
499
  rescue StandardError => e
481
- client_error(e, client)
500
+ client_error(e, client, requests)
482
501
  # The ensure tries to close +client+ down
483
502
  requests > 0
484
503
  ensure
485
- buffer.reset
504
+ client.io_buffer.reset
486
505
 
487
506
  begin
488
507
  client.close if close_socket
@@ -490,7 +509,7 @@ module Puma
490
509
  Puma::Util.purge_interrupt_queue
491
510
  # Already closed
492
511
  rescue StandardError => e
493
- @events.unknown_error e, nil, "Client"
512
+ @log_writer.unknown_error e, nil, "Client"
494
513
  end
495
514
  end
496
515
  end
@@ -506,23 +525,23 @@ module Puma
506
525
  # :nocov:
507
526
 
508
527
  # Handle various error types thrown by Client I/O operations.
509
- def client_error(e, client)
528
+ def client_error(e, client, requests = 1)
510
529
  # Swallow, do not log
511
530
  return if [ConnectionError, EOFError].include?(e.class)
512
531
 
513
- lowlevel_error(e, client.env)
514
532
  case e
515
533
  when MiniSSL::SSLError
516
- @events.ssl_error e, client.io
534
+ lowlevel_error(e, client.env)
535
+ @log_writer.ssl_error e, client.io
517
536
  when HttpParserError
518
- client.write_error(400)
519
- @events.parse_error e, client
537
+ response_to_error(client, requests, e, 400)
538
+ @log_writer.parse_error e, client
520
539
  when HttpParserError501
521
- client.write_error(501)
522
- @events.parse_error e, client
540
+ response_to_error(client, requests, e, 501)
541
+ @log_writer.parse_error e, client
523
542
  else
524
- client.write_error(500)
525
- @events.unknown_error e, nil, "Read"
543
+ response_to_error(client, requests, e, 500)
544
+ @log_writer.unknown_error e, nil, "Read"
526
545
  end
527
546
  end
528
547
 
@@ -543,10 +562,17 @@ module Puma
543
562
  backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
544
563
  [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
545
564
  else
546
- [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
565
+ [status, {}, [""]]
547
566
  end
548
567
  end
549
568
 
569
+ def response_to_error(client, requests, err, status_code)
570
+ status, headers, res_body = lowlevel_error(err, client.env, status_code)
571
+ prepare_response(status, headers, res_body, requests, client)
572
+ client.write_error(status_code)
573
+ end
574
+ private :response_to_error
575
+
550
576
  # Wait for all outstanding requests to finish.
551
577
  #
552
578
  def graceful_shutdown
@@ -580,7 +606,7 @@ module Puma
580
606
 
581
607
  def notify_safely(message)
582
608
  @notify << message
583
- rescue IOError, NoMethodError, Errno::EPIPE
609
+ rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
584
610
  # The server, in another thread, is shutting down
585
611
  Puma::Util.purge_interrupt_queue
586
612
  rescue RuntimeError => e
@@ -625,5 +651,27 @@ module Puma
625
651
  def stats
626
652
  STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
627
653
  end
654
+
655
+ # below are 'delegations' to binder
656
+ # remove in Puma 7?
657
+
658
+
659
+ def add_tcp_listener(host, port, optimize_for_latency = true, backlog = 1024)
660
+ @binder.add_tcp_listener host, port, optimize_for_latency, backlog
661
+ end
662
+
663
+ def add_ssl_listener(host, port, ctx, optimize_for_latency = true,
664
+ backlog = 1024)
665
+ @binder.add_ssl_listener host, port, ctx, optimize_for_latency, backlog
666
+ end
667
+
668
+ def add_unix_listener(path, umask = nil, mode = nil, backlog = 1024)
669
+ @binder.add_unix_listener path, umask, mode, backlog
670
+ end
671
+
672
+ # @!attribute [r] connected_ports
673
+ def connected_ports
674
+ @binder.connected_ports
675
+ end
628
676
  end
629
677
  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
@@ -59,11 +56,11 @@ module Puma
59
56
  end
60
57
 
61
58
  ALLOWED_FIELDS.each do |f|
62
- define_method f do
59
+ define_method f.to_sym do
63
60
  @options[f]
64
61
  end
65
62
 
66
- define_method "#{f}=" do |v|
63
+ define_method :"#{f}=" do |v|
67
64
  @options[f] = v
68
65
  end
69
66
  end
@@ -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)