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.
- checksums.yaml +7 -0
- data/puma-8.0.2/History.md +3334 -0
- data/puma-8.0.2/LICENSE +29 -0
- data/puma-8.0.2/README.md +484 -0
- data/puma-8.0.2/bin/puma +10 -0
- data/puma-8.0.2/bin/puma-wild +25 -0
- data/puma-8.0.2/bin/pumactl +12 -0
- data/puma-8.0.2/docs/5.0-Upgrade.md +98 -0
- data/puma-8.0.2/docs/6.0-Upgrade.md +56 -0
- data/puma-8.0.2/docs/7.0-Upgrade.md +52 -0
- data/puma-8.0.2/docs/8.0-Upgrade.md +100 -0
- data/puma-8.0.2/docs/architecture.md +74 -0
- data/puma-8.0.2/docs/compile_options.md +55 -0
- data/puma-8.0.2/docs/deployment.md +137 -0
- data/puma-8.0.2/docs/fork_worker.md +41 -0
- data/puma-8.0.2/docs/grpc.md +62 -0
- data/puma-8.0.2/docs/images/favicon.svg +1 -0
- data/puma-8.0.2/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/puma-8.0.2/docs/images/puma-connection-flow.png +0 -0
- data/puma-8.0.2/docs/images/puma-general-arch.png +0 -0
- data/puma-8.0.2/docs/images/running-puma.svg +1 -0
- data/puma-8.0.2/docs/images/standard-logo.svg +1 -0
- data/puma-8.0.2/docs/java_options.md +54 -0
- data/puma-8.0.2/docs/jungle/README.md +9 -0
- data/puma-8.0.2/docs/jungle/rc.d/README.md +74 -0
- data/puma-8.0.2/docs/jungle/rc.d/puma +61 -0
- data/puma-8.0.2/docs/jungle/rc.d/puma.conf +10 -0
- data/puma-8.0.2/docs/kubernetes.md +73 -0
- data/puma-8.0.2/docs/nginx.md +80 -0
- data/puma-8.0.2/docs/plugins.md +42 -0
- data/puma-8.0.2/docs/rails_dev_mode.md +28 -0
- data/puma-8.0.2/docs/restart.md +65 -0
- data/puma-8.0.2/docs/signals.md +98 -0
- data/puma-8.0.2/docs/stats.md +148 -0
- data/puma-8.0.2/docs/systemd.md +253 -0
- data/puma-8.0.2/docs/testing_benchmarks_local_files.md +150 -0
- data/puma-8.0.2/docs/testing_test_rackup_ci_files.md +36 -0
- data/puma-8.0.2/ext/puma_http11/PumaHttp11Service.java +17 -0
- data/puma-8.0.2/ext/puma_http11/extconf.rb +65 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser.c +1057 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser.h +65 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser.java.rl +131 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser.rl +149 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser_common.rl +54 -0
- data/puma-8.0.2/ext/puma_http11/mini_ssl.c +852 -0
- data/puma-8.0.2/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/puma-8.0.2/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/puma-8.0.2/ext/puma_http11/org/jruby/puma/Http11.java +321 -0
- data/puma-8.0.2/ext/puma_http11/org/jruby/puma/Http11Parser.java +441 -0
- data/puma-8.0.2/ext/puma_http11/org/jruby/puma/MiniSSL.java +509 -0
- data/puma-8.0.2/ext/puma_http11/puma_http11.c +499 -0
- data/puma-8.0.2/lib/puma/app/status.rb +104 -0
- data/puma-8.0.2/lib/puma/binder.rb +511 -0
- data/puma-8.0.2/lib/puma/cli.rb +245 -0
- data/puma-8.0.2/lib/puma/client.rb +756 -0
- data/puma-8.0.2/lib/puma/client_env.rb +171 -0
- data/puma-8.0.2/lib/puma/cluster/worker.rb +183 -0
- data/puma-8.0.2/lib/puma/cluster/worker_handle.rb +127 -0
- data/puma-8.0.2/lib/puma/cluster.rb +634 -0
- data/puma-8.0.2/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/puma-8.0.2/lib/puma/commonlogger.rb +115 -0
- data/puma-8.0.2/lib/puma/configuration.rb +522 -0
- data/puma-8.0.2/lib/puma/const.rb +308 -0
- data/puma-8.0.2/lib/puma/control_cli.rb +320 -0
- data/puma-8.0.2/lib/puma/detect.rb +58 -0
- data/puma-8.0.2/lib/puma/dsl.rb +1562 -0
- data/puma-8.0.2/lib/puma/error_logger.rb +115 -0
- data/puma-8.0.2/lib/puma/events.rb +72 -0
- data/puma-8.0.2/lib/puma/io_buffer.rb +50 -0
- data/puma-8.0.2/lib/puma/jruby_restart.rb +11 -0
- data/puma-8.0.2/lib/puma/json_serialization.rb +96 -0
- data/puma-8.0.2/lib/puma/launcher/bundle_pruner.rb +102 -0
- data/puma-8.0.2/lib/puma/launcher.rb +501 -0
- data/puma-8.0.2/lib/puma/log_writer.rb +153 -0
- data/puma-8.0.2/lib/puma/minissl/context_builder.rb +96 -0
- data/puma-8.0.2/lib/puma/minissl.rb +458 -0
- data/puma-8.0.2/lib/puma/null_io.rb +101 -0
- data/puma-8.0.2/lib/puma/plugin/systemd.rb +90 -0
- data/puma-8.0.2/lib/puma/plugin/tmp_restart.rb +36 -0
- data/puma-8.0.2/lib/puma/plugin.rb +111 -0
- data/puma-8.0.2/lib/puma/rack/builder.rb +297 -0
- data/puma-8.0.2/lib/puma/rack/urlmap.rb +93 -0
- data/puma-8.0.2/lib/puma/rack_default.rb +24 -0
- data/puma-8.0.2/lib/puma/reactor.rb +131 -0
- data/puma-8.0.2/lib/puma/response.rb +532 -0
- data/puma-8.0.2/lib/puma/runner.rb +211 -0
- data/puma-8.0.2/lib/puma/sd_notify.rb +146 -0
- data/puma-8.0.2/lib/puma/server.rb +773 -0
- data/puma-8.0.2/lib/puma/server_plugin_control.rb +32 -0
- data/puma-8.0.2/lib/puma/single.rb +72 -0
- data/puma-8.0.2/lib/puma/state_file.rb +69 -0
- data/puma-8.0.2/lib/puma/thread_pool.rb +517 -0
- data/puma-8.0.2/lib/puma/util.rb +134 -0
- data/puma-8.0.2/lib/puma.rb +88 -0
- data/puma-8.0.2/lib/rack/handler/puma.rb +144 -0
- data/puma-8.0.2/tools/Dockerfile +26 -0
- data/puma-8.0.2/tools/trickletest.rb +44 -0
- data/tiny-fast-gem.gemspec +12 -0
- 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
|