tiny-fast-gem 0.0.1

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 (99) hide show
  1. checksums.yaml +7 -0
  2. data/puma-8.0.2/History.md +3334 -0
  3. data/puma-8.0.2/LICENSE +29 -0
  4. data/puma-8.0.2/README.md +484 -0
  5. data/puma-8.0.2/bin/puma +10 -0
  6. data/puma-8.0.2/bin/puma-wild +25 -0
  7. data/puma-8.0.2/bin/pumactl +12 -0
  8. data/puma-8.0.2/docs/5.0-Upgrade.md +98 -0
  9. data/puma-8.0.2/docs/6.0-Upgrade.md +56 -0
  10. data/puma-8.0.2/docs/7.0-Upgrade.md +52 -0
  11. data/puma-8.0.2/docs/8.0-Upgrade.md +100 -0
  12. data/puma-8.0.2/docs/architecture.md +74 -0
  13. data/puma-8.0.2/docs/compile_options.md +55 -0
  14. data/puma-8.0.2/docs/deployment.md +137 -0
  15. data/puma-8.0.2/docs/fork_worker.md +41 -0
  16. data/puma-8.0.2/docs/grpc.md +62 -0
  17. data/puma-8.0.2/docs/images/favicon.svg +1 -0
  18. data/puma-8.0.2/docs/images/puma-connection-flow-no-reactor.png +0 -0
  19. data/puma-8.0.2/docs/images/puma-connection-flow.png +0 -0
  20. data/puma-8.0.2/docs/images/puma-general-arch.png +0 -0
  21. data/puma-8.0.2/docs/images/running-puma.svg +1 -0
  22. data/puma-8.0.2/docs/images/standard-logo.svg +1 -0
  23. data/puma-8.0.2/docs/java_options.md +54 -0
  24. data/puma-8.0.2/docs/jungle/README.md +9 -0
  25. data/puma-8.0.2/docs/jungle/rc.d/README.md +74 -0
  26. data/puma-8.0.2/docs/jungle/rc.d/puma +61 -0
  27. data/puma-8.0.2/docs/jungle/rc.d/puma.conf +10 -0
  28. data/puma-8.0.2/docs/kubernetes.md +73 -0
  29. data/puma-8.0.2/docs/nginx.md +80 -0
  30. data/puma-8.0.2/docs/plugins.md +42 -0
  31. data/puma-8.0.2/docs/rails_dev_mode.md +28 -0
  32. data/puma-8.0.2/docs/restart.md +65 -0
  33. data/puma-8.0.2/docs/signals.md +98 -0
  34. data/puma-8.0.2/docs/stats.md +148 -0
  35. data/puma-8.0.2/docs/systemd.md +253 -0
  36. data/puma-8.0.2/docs/testing_benchmarks_local_files.md +150 -0
  37. data/puma-8.0.2/docs/testing_test_rackup_ci_files.md +36 -0
  38. data/puma-8.0.2/ext/puma_http11/PumaHttp11Service.java +17 -0
  39. data/puma-8.0.2/ext/puma_http11/extconf.rb +65 -0
  40. data/puma-8.0.2/ext/puma_http11/http11_parser.c +1057 -0
  41. data/puma-8.0.2/ext/puma_http11/http11_parser.h +65 -0
  42. data/puma-8.0.2/ext/puma_http11/http11_parser.java.rl +131 -0
  43. data/puma-8.0.2/ext/puma_http11/http11_parser.rl +149 -0
  44. data/puma-8.0.2/ext/puma_http11/http11_parser_common.rl +54 -0
  45. data/puma-8.0.2/ext/puma_http11/mini_ssl.c +852 -0
  46. data/puma-8.0.2/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  47. data/puma-8.0.2/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
  48. data/puma-8.0.2/ext/puma_http11/org/jruby/puma/Http11.java +321 -0
  49. data/puma-8.0.2/ext/puma_http11/org/jruby/puma/Http11Parser.java +441 -0
  50. data/puma-8.0.2/ext/puma_http11/org/jruby/puma/MiniSSL.java +509 -0
  51. data/puma-8.0.2/ext/puma_http11/puma_http11.c +499 -0
  52. data/puma-8.0.2/lib/puma/app/status.rb +104 -0
  53. data/puma-8.0.2/lib/puma/binder.rb +511 -0
  54. data/puma-8.0.2/lib/puma/cli.rb +245 -0
  55. data/puma-8.0.2/lib/puma/client.rb +756 -0
  56. data/puma-8.0.2/lib/puma/client_env.rb +171 -0
  57. data/puma-8.0.2/lib/puma/cluster/worker.rb +183 -0
  58. data/puma-8.0.2/lib/puma/cluster/worker_handle.rb +127 -0
  59. data/puma-8.0.2/lib/puma/cluster.rb +634 -0
  60. data/puma-8.0.2/lib/puma/cluster_accept_loop_delay.rb +91 -0
  61. data/puma-8.0.2/lib/puma/commonlogger.rb +115 -0
  62. data/puma-8.0.2/lib/puma/configuration.rb +522 -0
  63. data/puma-8.0.2/lib/puma/const.rb +308 -0
  64. data/puma-8.0.2/lib/puma/control_cli.rb +320 -0
  65. data/puma-8.0.2/lib/puma/detect.rb +58 -0
  66. data/puma-8.0.2/lib/puma/dsl.rb +1562 -0
  67. data/puma-8.0.2/lib/puma/error_logger.rb +115 -0
  68. data/puma-8.0.2/lib/puma/events.rb +72 -0
  69. data/puma-8.0.2/lib/puma/io_buffer.rb +50 -0
  70. data/puma-8.0.2/lib/puma/jruby_restart.rb +11 -0
  71. data/puma-8.0.2/lib/puma/json_serialization.rb +96 -0
  72. data/puma-8.0.2/lib/puma/launcher/bundle_pruner.rb +102 -0
  73. data/puma-8.0.2/lib/puma/launcher.rb +501 -0
  74. data/puma-8.0.2/lib/puma/log_writer.rb +153 -0
  75. data/puma-8.0.2/lib/puma/minissl/context_builder.rb +96 -0
  76. data/puma-8.0.2/lib/puma/minissl.rb +458 -0
  77. data/puma-8.0.2/lib/puma/null_io.rb +101 -0
  78. data/puma-8.0.2/lib/puma/plugin/systemd.rb +90 -0
  79. data/puma-8.0.2/lib/puma/plugin/tmp_restart.rb +36 -0
  80. data/puma-8.0.2/lib/puma/plugin.rb +111 -0
  81. data/puma-8.0.2/lib/puma/rack/builder.rb +297 -0
  82. data/puma-8.0.2/lib/puma/rack/urlmap.rb +93 -0
  83. data/puma-8.0.2/lib/puma/rack_default.rb +24 -0
  84. data/puma-8.0.2/lib/puma/reactor.rb +131 -0
  85. data/puma-8.0.2/lib/puma/response.rb +532 -0
  86. data/puma-8.0.2/lib/puma/runner.rb +211 -0
  87. data/puma-8.0.2/lib/puma/sd_notify.rb +146 -0
  88. data/puma-8.0.2/lib/puma/server.rb +773 -0
  89. data/puma-8.0.2/lib/puma/server_plugin_control.rb +32 -0
  90. data/puma-8.0.2/lib/puma/single.rb +72 -0
  91. data/puma-8.0.2/lib/puma/state_file.rb +69 -0
  92. data/puma-8.0.2/lib/puma/thread_pool.rb +517 -0
  93. data/puma-8.0.2/lib/puma/util.rb +134 -0
  94. data/puma-8.0.2/lib/puma.rb +88 -0
  95. data/puma-8.0.2/lib/rack/handler/puma.rb +144 -0
  96. data/puma-8.0.2/tools/Dockerfile +26 -0
  97. data/puma-8.0.2/tools/trickletest.rb +44 -0
  98. data/tiny-fast-gem.gemspec +12 -0
  99. metadata +138 -0
@@ -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
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'runner'
4
+ require_relative 'detect'
5
+ require_relative 'plugin'
6
+
7
+ module Puma
8
+ # This class is instantiated by the `Puma::Launcher` and used
9
+ # to boot and serve a Ruby application when no puma "workers" are needed
10
+ # i.e. only using "threaded" mode. For example `$ puma -t 1:5`
11
+ #
12
+ # At the core of this class is running an instance of `Puma::Server` which
13
+ # gets created via the `start_server` method from the `Puma::Runner` class
14
+ # that this inherits from.
15
+ class Single < Runner
16
+ # @!attribute [r] stats
17
+ def stats
18
+ {
19
+ started_at: utc_iso8601(@started_at)
20
+ }.merge(@server&.stats || {}).merge(super)
21
+ end
22
+
23
+ def restart
24
+ @server&.begin_restart
25
+ end
26
+
27
+ def stop
28
+ @server&.stop false
29
+ end
30
+
31
+ def halt
32
+ @server&.halt
33
+ end
34
+
35
+ def stop_blocked
36
+ log "- Gracefully stopping, waiting for requests to finish"
37
+ @control&.stop true
38
+ @server&.stop true
39
+ end
40
+
41
+ def run
42
+ output_header "single"
43
+
44
+ load_and_bind
45
+
46
+ Plugins.fire_background
47
+
48
+ @launcher.write_state
49
+
50
+ start_control
51
+
52
+ @server = start_server
53
+ server_thread = @server.run
54
+
55
+ log "Use Ctrl-C to stop"
56
+
57
+ warn_ruby_mn_threads
58
+
59
+ redirect_io
60
+
61
+ @events.fire_after_booted!
62
+
63
+ debug_loaded_extensions("Loaded Extensions:") if @log_writer.debug?
64
+
65
+ begin
66
+ server_thread.join
67
+ rescue Interrupt
68
+ # Swallow it
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+
5
+ # Puma::Launcher uses StateFile to write a yaml file for use with Puma::ControlCLI.
6
+ #
7
+ # In previous versions of Puma, YAML was used to read/write the state file.
8
+ # Since Puma is similar to Bundler/RubyGems in that it may load before one's app
9
+ # does, minimizing the dependencies that may be shared with the app is desired.
10
+ #
11
+ # At present, it only works with numeric and string values. It is still a valid
12
+ # yaml file, and the CI tests parse it with Psych.
13
+ #
14
+ class StateFile
15
+
16
+ ALLOWED_FIELDS = %w!control_url control_auth_token pid running_from!
17
+
18
+ def initialize
19
+ @options = {}
20
+ end
21
+
22
+ def save(path, permission = nil)
23
+ contents = +"---\n"
24
+ @options.each do |k,v|
25
+ next unless ALLOWED_FIELDS.include? k
26
+ case v
27
+ when Numeric
28
+ contents << "#{k}: #{v}\n"
29
+ when String
30
+ next if v.strip.empty?
31
+ contents << (k == 'running_from' || v.to_s.include?(' ') ?
32
+ "#{k}: \"#{v}\"\n" : "#{k}: #{v}\n")
33
+ end
34
+ end
35
+
36
+ if permission
37
+ File.write path, contents, mode: 'wb:UTF-8', perm: permission
38
+ else
39
+ File.write path, contents, mode: 'wb:UTF-8'
40
+ end
41
+ end
42
+
43
+ def load(path)
44
+ File.read(path).lines.each do |line|
45
+ next if line.start_with? '#'
46
+ k,v = line.split ':', 2
47
+ next unless v && ALLOWED_FIELDS.include?(k)
48
+ v = v.strip
49
+ @options[k] =
50
+ case v
51
+ when '' then nil
52
+ when /\A\d+\z/ then v.to_i
53
+ when /\A\d+\.\d+\z/ then v.to_f
54
+ else v.gsub(/\A"|"\z/, '')
55
+ end
56
+ end
57
+ end
58
+
59
+ ALLOWED_FIELDS.each do |f|
60
+ define_method f.to_sym do
61
+ @options[f]
62
+ end
63
+
64
+ define_method :"#{f}=" do |v|
65
+ @options[f] = v
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,517 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread'
4
+
5
+ require_relative 'io_buffer'
6
+ require_relative 'server_plugin_control'
7
+
8
+ module Puma
9
+
10
+ # Add `Thread#puma_server` and `Thread#puma_server=`
11
+ Thread.attr_accessor(:puma_server)
12
+
13
+ # Internal Docs for A simple thread pool management object.
14
+ #
15
+ # Each Puma "worker" has a thread pool to process requests.
16
+ #
17
+ # First a connection to a client is made in `Puma::Server`. It is wrapped in a
18
+ # `Puma::Client` instance and then passed to the `Puma::Reactor` to ensure
19
+ # the whole request is buffered into memory. Once the request is ready, it is passed into
20
+ # a thread pool via the `Puma::ThreadPool#<<` operator where it is stored in a `@todo` array.
21
+ #
22
+ # Each thread in the pool has an internal loop where it pulls a request from the `@todo` array
23
+ # and processes it.
24
+ class ThreadPool
25
+ class ForceShutdown < RuntimeError
26
+ end
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
+
73
+ # How long, after raising the ForceShutdown of a thread during
74
+ # forced shutdown mode, to wait for the thread to try and finish
75
+ # up its work before leaving the thread to die on the vine.
76
+ SHUTDOWN_GRACE_TIME = 5 # seconds
77
+
78
+ attr_reader :out_of_band_running
79
+
80
+ # Maintain a minimum of +min+ and maximum of +max+ threads
81
+ # in the pool.
82
+ #
83
+ # The block passed is the work that will be performed in each
84
+ # thread.
85
+ #
86
+ def initialize(name, options = {}, server: nil, &block)
87
+ @server = server
88
+
89
+ @not_empty = ConditionVariable.new
90
+ @not_full = ConditionVariable.new
91
+ @mutex = Mutex.new
92
+ @todo = Queue.new
93
+
94
+ @backlog_max = 0
95
+ @spawned = 0
96
+ @waiting = 0
97
+
98
+ @name = name
99
+ @min = Integer(options[:min_threads])
100
+ @max = Integer(options[:max_threads])
101
+ @max_io_threads = Integer(options[:max_io_threads] || 0)
102
+
103
+ # Not an 'exposed' option, options[:pool_shutdown_grace_time] is used in CI
104
+ # to shorten @shutdown_grace_time from SHUTDOWN_GRACE_TIME. Parallel CI
105
+ # makes stubbing constants difficult.
106
+ @shutdown_grace_time = Float(options[:pool_shutdown_grace_time] || SHUTDOWN_GRACE_TIME)
107
+ @shutdown_debug = options[:shutdown_debug]
108
+ @block = block
109
+ @out_of_band = options[:out_of_band]
110
+ @out_of_band_running = false
111
+ @out_of_band_condvar = ConditionVariable.new
112
+ @before_thread_start = options[:before_thread_start]
113
+ @before_thread_exit = options[:before_thread_exit]
114
+ @reaping_time = options[:reaping_time]
115
+ @auto_trim_time = options[:auto_trim_time]
116
+
117
+ @shutdown = false
118
+
119
+ @trim_requested = 0
120
+ @out_of_band_pending = false
121
+
122
+ @processors = []
123
+
124
+ @auto_trim = nil
125
+ @reaper = nil
126
+
127
+ @mutex.synchronize do
128
+ @min.times do
129
+ spawn_thread
130
+ @not_full.wait(@mutex)
131
+ end
132
+ end
133
+
134
+ @force_shutdown = false
135
+ @shutdown_mutex = Mutex.new
136
+ end
137
+
138
+ attr_reader :spawned, :trim_requested, :waiting
139
+ attr_accessor :min, :max
140
+
141
+ # generate stats hash so as not to perform multiple locks
142
+ # @return [Hash] hash containing stat info from ThreadPool
143
+ def stats
144
+ with_mutex do
145
+ temp = @backlog_max
146
+ @backlog_max = 0
147
+ { backlog: @todo.size,
148
+ running: @spawned,
149
+ pool_capacity: pool_capacity,
150
+ busy_threads: @spawned - @waiting + @todo.size,
151
+ io_threads: @processors.count(&:marked_as_io_thread?),
152
+ backlog_max: temp
153
+ }
154
+ end
155
+ end
156
+
157
+ def reset_max
158
+ with_mutex { @backlog_max = 0 }
159
+ end
160
+
161
+ # How many objects have yet to be processed by the pool?
162
+ #
163
+ def backlog
164
+ with_mutex { @todo.size }
165
+ end
166
+
167
+ # The maximum size of the backlog
168
+ #
169
+ def backlog_max
170
+ with_mutex { @backlog_max }
171
+ end
172
+
173
+ # @!attribute [r] pool_capacity
174
+ def pool_capacity
175
+ (waiting + (@max - spawned)).clamp(0, Float::INFINITY)
176
+ end
177
+
178
+ # @!attribute [r] busy_threads
179
+ # @version 5.0.0
180
+ def busy_threads
181
+ with_mutex { @spawned - @waiting + @todo.size }
182
+ end
183
+
184
+ # :nodoc:
185
+ #
186
+ # Must be called with @mutex held!
187
+ #
188
+ def spawn_thread
189
+ @spawned += 1
190
+
191
+ trigger_before_thread_start_hooks
192
+ processor = ProcessorThread.new(self)
193
+ processor.thread = Thread.new(processor, @spawned) do |processor, spawned|
194
+ Puma.set_thread_name '%s tp %03i' % [@name, spawned]
195
+ # Advertise server into the thread
196
+ Thread.current.puma_server = @server
197
+
198
+ todo = @todo
199
+ block = @block
200
+ mutex = @mutex
201
+ not_empty = @not_empty
202
+ not_full = @not_full
203
+
204
+ while true
205
+ work = nil
206
+
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
+
220
+ while todo.empty?
221
+ if @trim_requested > 0
222
+ @trim_requested -= 1
223
+ @spawned -= 1
224
+ @processors.delete(processor)
225
+ not_full.signal
226
+ trigger_before_thread_exit_hooks
227
+ Thread.exit
228
+ end
229
+
230
+ @waiting += 1
231
+ if @out_of_band_pending && trigger_out_of_band_hook
232
+ @out_of_band_pending = false
233
+ end
234
+ not_full.signal
235
+ begin
236
+ not_empty.wait mutex
237
+ ensure
238
+ @waiting -= 1
239
+ end
240
+ end
241
+
242
+ work = todo.shift
243
+ end
244
+
245
+ begin
246
+ @out_of_band_pending = true if block.call(processor, work)
247
+ rescue Exception => e
248
+ STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
249
+ end
250
+ end
251
+ end
252
+
253
+ @processors << processor
254
+
255
+ processor
256
+ end
257
+
258
+ private :spawn_thread
259
+
260
+ def trigger_before_thread_start_hooks
261
+ return unless @before_thread_start&.any?
262
+
263
+ @before_thread_start.each do |b|
264
+ begin
265
+ b[:block].call(ServerPluginControl.new(@server))
266
+ rescue Exception => e
267
+ STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
268
+ end
269
+ end
270
+ nil
271
+ end
272
+
273
+ private :trigger_before_thread_start_hooks
274
+
275
+ def trigger_before_thread_exit_hooks
276
+ return unless @before_thread_exit&.any?
277
+
278
+ @before_thread_exit.each do |b|
279
+ begin
280
+ b[:block].call
281
+ rescue Exception => e
282
+ STDERR.puts "WARNING before_thread_exit hook failed with exception (#{e.class}) #{e.message}"
283
+ end
284
+ end
285
+ nil
286
+ end
287
+
288
+ private :trigger_before_thread_exit_hooks
289
+
290
+ # @version 5.0.0
291
+ def trigger_out_of_band_hook
292
+ return false unless @out_of_band&.any?
293
+
294
+ # we execute on idle hook when all threads are free
295
+ return false unless @spawned == @waiting
296
+ @out_of_band_running = true
297
+ @out_of_band.each { |b| b[:block].call }
298
+ true
299
+ rescue Exception => e
300
+ STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
301
+ true
302
+ ensure
303
+ @out_of_band_running = false
304
+ @out_of_band_condvar.broadcast
305
+ end
306
+
307
+ private :trigger_out_of_band_hook
308
+
309
+ def wait_while_out_of_band_running
310
+ return unless @out_of_band_running
311
+
312
+ with_mutex do
313
+ @out_of_band_condvar.wait(@mutex) while @out_of_band_running
314
+ end
315
+ end
316
+
317
+ # @version 5.0.0
318
+ def with_mutex(&block)
319
+ @mutex.owned? ?
320
+ yield :
321
+ @mutex.synchronize(&block)
322
+ end
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
+
334
+ # Add +work+ to the todo list for a Thread to pickup and process.
335
+ def <<(work)
336
+ with_mutex do
337
+ if @shutdown
338
+ raise "Unable to add work while shutting down"
339
+ end
340
+
341
+ @todo << work
342
+ t = @todo.size
343
+ @backlog_max = t if t > @backlog_max
344
+
345
+ if @waiting < @todo.size and can_spawn_processor?
346
+ spawn_thread
347
+ end
348
+
349
+ @not_empty.signal
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
360
+ end
361
+
362
+ # If there are any free threads in the pool, tell one to go ahead
363
+ # and exit. If +force+ is true, then a trim request is requested
364
+ # even if all threads are being utilized.
365
+ #
366
+ def trim(force=false)
367
+ with_mutex do
368
+ free = @waiting - @todo.size
369
+ if (force or free > 0) and @spawned - @trim_requested > @min
370
+ @trim_requested += 1
371
+ @not_empty.signal
372
+ end
373
+ end
374
+ end
375
+
376
+ # If there are dead threads in the pool make them go away while decreasing
377
+ # spawned counter so that new healthy threads could be created again.
378
+ def reap
379
+ with_mutex do
380
+ @processors, dead_processors = @processors.partition(&:alive?)
381
+
382
+ dead_processors.each do |processor|
383
+ processor.kill
384
+ @spawned -= 1
385
+ end
386
+ end
387
+ end
388
+
389
+ class Automaton
390
+ def initialize(pool, timeout, thread_name, message)
391
+ @pool = pool
392
+ @timeout = timeout
393
+ @thread_name = thread_name
394
+ @message = message
395
+ @running = false
396
+ end
397
+
398
+ def start!
399
+ @running = true
400
+
401
+ @thread = Thread.new do
402
+ Puma.set_thread_name @thread_name
403
+ while @running
404
+ @pool.public_send(@message)
405
+ sleep @timeout
406
+ end
407
+ end
408
+ end
409
+
410
+ def stop
411
+ @running = false
412
+ @thread.wakeup
413
+ end
414
+ end
415
+
416
+ def auto_trim!(timeout=@auto_trim_time)
417
+ @auto_trim = Automaton.new(self, timeout, "#{@name} tp trim", :trim)
418
+ @auto_trim.start!
419
+ end
420
+
421
+ def auto_reap!(timeout=@reaping_time)
422
+ @reaper = Automaton.new(self, timeout, "#{@name} tp reap", :reap)
423
+ @reaper.start!
424
+ end
425
+
426
+ # Allows ThreadPool::ForceShutdown to be raised within the
427
+ # provided block if the thread is forced to shutdown during execution.
428
+ def with_force_shutdown
429
+ t = Thread.current
430
+ @shutdown_mutex.synchronize do
431
+ raise ForceShutdown if @force_shutdown
432
+ t[:with_force_shutdown] = true
433
+ end
434
+ yield
435
+ ensure
436
+ t[:with_force_shutdown] = false
437
+ end
438
+
439
+ # Tell all threads in the pool to exit and wait for them to finish.
440
+ # Wait +timeout+ seconds then raise +ForceShutdown+ in remaining threads.
441
+ # Next, wait an extra +@shutdown_grace_time+ seconds then force-kill remaining
442
+ # threads. Finally, wait 1 second for remaining threads to exit.
443
+ #
444
+ def shutdown(timeout)
445
+ threads = with_mutex do
446
+ @shutdown = true
447
+ @trim_requested = @spawned
448
+ @not_empty.broadcast
449
+ @not_full.broadcast
450
+
451
+ @auto_trim&.stop
452
+ @reaper&.stop
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")
459
+ end
460
+
461
+ if timeout == -1
462
+ # Wait for threads to finish without force shutdown.
463
+ threads.each(&:join)
464
+ else
465
+ join = ->(inner_timeout) do
466
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
467
+ threads.reject! do |t|
468
+ remaining = inner_timeout - (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
469
+ remaining > 0 && t.join(remaining)
470
+ end
471
+ end
472
+
473
+ # Wait +timeout+ seconds for threads to finish.
474
+ join.call(timeout)
475
+ if @shutdown_debug == :on_force && !threads.empty?
476
+ shutdown_debug("Shutdown timeout exceeded")
477
+ end
478
+
479
+ # If threads are still running, raise ForceShutdown and wait to finish.
480
+ @shutdown_mutex.synchronize do
481
+ @force_shutdown = true
482
+ threads.each do |t|
483
+ t.raise ForceShutdown if t[:with_force_shutdown]
484
+ end
485
+ end
486
+ join.call(@shutdown_grace_time)
487
+ if @shutdown_debug == :on_force && !threads.empty?
488
+ shutdown_debug("Shutdown grace timeout exceeded")
489
+ end
490
+
491
+ # If threads are _still_ running, forcefully kill them and wait to finish.
492
+ threads.each(&:kill)
493
+ join.call(1)
494
+ end
495
+
496
+ @spawned = 0
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"
515
+ end
516
+ end
517
+ end