ed-precompiled_puma 7.0.4
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/History.md +3172 -0
- data/LICENSE +29 -0
- data/README.md +477 -0
- data/bin/puma +10 -0
- data/bin/puma-wild +25 -0
- data/bin/pumactl +12 -0
- data/docs/architecture.md +74 -0
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +102 -0
- data/docs/fork_worker.md +41 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/java_options.md +54 -0
- data/docs/jungle/README.md +9 -0
- data/docs/jungle/rc.d/README.md +74 -0
- data/docs/jungle/rc.d/puma +61 -0
- data/docs/jungle/rc.d/puma.conf +10 -0
- data/docs/kubernetes.md +80 -0
- data/docs/nginx.md +80 -0
- data/docs/plugins.md +42 -0
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +65 -0
- data/docs/signals.md +98 -0
- data/docs/stats.md +148 -0
- data/docs/systemd.md +253 -0
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/PumaHttp11Service.java +17 -0
- data/ext/puma_http11/ext_help.h +15 -0
- data/ext/puma_http11/extconf.rb +65 -0
- data/ext/puma_http11/http11_parser.c +1057 -0
- data/ext/puma_http11/http11_parser.h +65 -0
- data/ext/puma_http11/http11_parser.java.rl +145 -0
- data/ext/puma_http11/http11_parser.rl +149 -0
- data/ext/puma_http11/http11_parser_common.rl +54 -0
- data/ext/puma_http11/mini_ssl.c +852 -0
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +257 -0
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +455 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +509 -0
- data/ext/puma_http11/puma_http11.c +507 -0
- data/lib/puma/app/status.rb +96 -0
- data/lib/puma/binder.rb +511 -0
- data/lib/puma/cli.rb +245 -0
- data/lib/puma/client.rb +720 -0
- data/lib/puma/cluster/worker.rb +182 -0
- data/lib/puma/cluster/worker_handle.rb +127 -0
- data/lib/puma/cluster.rb +635 -0
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +115 -0
- data/lib/puma/configuration.rb +452 -0
- data/lib/puma/const.rb +307 -0
- data/lib/puma/control_cli.rb +320 -0
- data/lib/puma/detect.rb +47 -0
- data/lib/puma/dsl.rb +1480 -0
- data/lib/puma/error_logger.rb +115 -0
- data/lib/puma/events.rb +72 -0
- data/lib/puma/io_buffer.rb +50 -0
- data/lib/puma/jruby_restart.rb +11 -0
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +496 -0
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +96 -0
- data/lib/puma/minissl.rb +463 -0
- data/lib/puma/null_io.rb +101 -0
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +36 -0
- data/lib/puma/plugin.rb +111 -0
- data/lib/puma/rack/builder.rb +297 -0
- data/lib/puma/rack/urlmap.rb +93 -0
- data/lib/puma/rack_default.rb +24 -0
- data/lib/puma/reactor.rb +140 -0
- data/lib/puma/request.rb +701 -0
- data/lib/puma/runner.rb +211 -0
- data/lib/puma/sd_notify.rb +146 -0
- data/lib/puma/server.rb +734 -0
- data/lib/puma/single.rb +72 -0
- data/lib/puma/state_file.rb +69 -0
- data/lib/puma/thread_pool.rb +402 -0
- data/lib/puma/util.rb +134 -0
- data/lib/puma.rb +93 -0
- data/lib/rack/handler/puma.rb +144 -0
- data/tools/Dockerfile +18 -0
- data/tools/trickletest.rb +44 -0
- metadata +152 -0
data/lib/puma/single.rb
ADDED
@@ -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 = 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,402 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
require_relative 'io_buffer'
|
6
|
+
|
7
|
+
module Puma
|
8
|
+
# Internal Docs for A simple thread pool management object.
|
9
|
+
#
|
10
|
+
# Each Puma "worker" has a thread pool to process requests.
|
11
|
+
#
|
12
|
+
# First a connection to a client is made in `Puma::Server`. It is wrapped in a
|
13
|
+
# `Puma::Client` instance and then passed to the `Puma::Reactor` to ensure
|
14
|
+
# the whole request is buffered into memory. Once the request is ready, it is passed into
|
15
|
+
# a thread pool via the `Puma::ThreadPool#<<` operator where it is stored in a `@todo` array.
|
16
|
+
#
|
17
|
+
# Each thread in the pool has an internal loop where it pulls a request from the `@todo` array
|
18
|
+
# and processes it.
|
19
|
+
class ThreadPool
|
20
|
+
class ForceShutdown < RuntimeError
|
21
|
+
end
|
22
|
+
|
23
|
+
# How long, after raising the ForceShutdown of a thread during
|
24
|
+
# forced shutdown mode, to wait for the thread to try and finish
|
25
|
+
# up its work before leaving the thread to die on the vine.
|
26
|
+
SHUTDOWN_GRACE_TIME = 5 # seconds
|
27
|
+
|
28
|
+
attr_reader :out_of_band_running
|
29
|
+
|
30
|
+
# Maintain a minimum of +min+ and maximum of +max+ threads
|
31
|
+
# in the pool.
|
32
|
+
#
|
33
|
+
# The block passed is the work that will be performed in each
|
34
|
+
# thread.
|
35
|
+
#
|
36
|
+
def initialize(name, options = {}, &block)
|
37
|
+
@not_empty = ConditionVariable.new
|
38
|
+
@not_full = ConditionVariable.new
|
39
|
+
@mutex = Mutex.new
|
40
|
+
@todo = Queue.new
|
41
|
+
|
42
|
+
@backlog_max = 0
|
43
|
+
@spawned = 0
|
44
|
+
@waiting = 0
|
45
|
+
|
46
|
+
@name = name
|
47
|
+
@min = Integer(options[:min_threads])
|
48
|
+
@max = Integer(options[:max_threads])
|
49
|
+
# Not an 'exposed' option, options[:pool_shutdown_grace_time] is used in CI
|
50
|
+
# to shorten @shutdown_grace_time from SHUTDOWN_GRACE_TIME. Parallel CI
|
51
|
+
# makes stubbing constants difficult.
|
52
|
+
@shutdown_grace_time = Float(options[:pool_shutdown_grace_time] || SHUTDOWN_GRACE_TIME)
|
53
|
+
@block = block
|
54
|
+
@out_of_band = options[:out_of_band]
|
55
|
+
@out_of_band_running = false
|
56
|
+
@out_of_band_condvar = ConditionVariable.new
|
57
|
+
@before_thread_start = options[:before_thread_start]
|
58
|
+
@before_thread_exit = options[:before_thread_exit]
|
59
|
+
@reaping_time = options[:reaping_time]
|
60
|
+
@auto_trim_time = options[:auto_trim_time]
|
61
|
+
|
62
|
+
@shutdown = false
|
63
|
+
|
64
|
+
@trim_requested = 0
|
65
|
+
@out_of_band_pending = false
|
66
|
+
|
67
|
+
@workers = []
|
68
|
+
|
69
|
+
@auto_trim = nil
|
70
|
+
@reaper = nil
|
71
|
+
|
72
|
+
@mutex.synchronize do
|
73
|
+
@min.times do
|
74
|
+
spawn_thread
|
75
|
+
@not_full.wait(@mutex)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
@force_shutdown = false
|
80
|
+
@shutdown_mutex = Mutex.new
|
81
|
+
end
|
82
|
+
|
83
|
+
attr_reader :spawned, :trim_requested, :waiting
|
84
|
+
|
85
|
+
# generate stats hash so as not to perform multiple locks
|
86
|
+
# @return [Hash] hash containing stat info from ThreadPool
|
87
|
+
def stats
|
88
|
+
with_mutex do
|
89
|
+
temp = @backlog_max
|
90
|
+
@backlog_max = 0
|
91
|
+
{ backlog: @todo.size,
|
92
|
+
running: @spawned,
|
93
|
+
pool_capacity: @waiting + (@max - @spawned),
|
94
|
+
busy_threads: @spawned - @waiting + @todo.size,
|
95
|
+
backlog_max: temp
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def reset_max
|
101
|
+
with_mutex { @backlog_max = 0 }
|
102
|
+
end
|
103
|
+
|
104
|
+
# How many objects have yet to be processed by the pool?
|
105
|
+
#
|
106
|
+
def backlog
|
107
|
+
with_mutex { @todo.size }
|
108
|
+
end
|
109
|
+
|
110
|
+
# The maximum size of the backlog
|
111
|
+
#
|
112
|
+
def backlog_max
|
113
|
+
with_mutex { @backlog_max }
|
114
|
+
end
|
115
|
+
|
116
|
+
# @!attribute [r] pool_capacity
|
117
|
+
def pool_capacity
|
118
|
+
waiting + (@max - spawned)
|
119
|
+
end
|
120
|
+
|
121
|
+
# @!attribute [r] busy_threads
|
122
|
+
# @version 5.0.0
|
123
|
+
def busy_threads
|
124
|
+
with_mutex { @spawned - @waiting + @todo.size }
|
125
|
+
end
|
126
|
+
|
127
|
+
# :nodoc:
|
128
|
+
#
|
129
|
+
# Must be called with @mutex held!
|
130
|
+
#
|
131
|
+
def spawn_thread
|
132
|
+
@spawned += 1
|
133
|
+
|
134
|
+
trigger_before_thread_start_hooks
|
135
|
+
th = Thread.new(@spawned) do |spawned|
|
136
|
+
Puma.set_thread_name '%s tp %03i' % [@name, spawned]
|
137
|
+
todo = @todo
|
138
|
+
block = @block
|
139
|
+
mutex = @mutex
|
140
|
+
not_empty = @not_empty
|
141
|
+
not_full = @not_full
|
142
|
+
|
143
|
+
while true
|
144
|
+
work = nil
|
145
|
+
|
146
|
+
mutex.synchronize do
|
147
|
+
while todo.empty?
|
148
|
+
if @trim_requested > 0
|
149
|
+
@trim_requested -= 1
|
150
|
+
@spawned -= 1
|
151
|
+
@workers.delete th
|
152
|
+
not_full.signal
|
153
|
+
trigger_before_thread_exit_hooks
|
154
|
+
Thread.exit
|
155
|
+
end
|
156
|
+
|
157
|
+
@waiting += 1
|
158
|
+
if @out_of_band_pending && trigger_out_of_band_hook
|
159
|
+
@out_of_band_pending = false
|
160
|
+
end
|
161
|
+
not_full.signal
|
162
|
+
begin
|
163
|
+
not_empty.wait mutex
|
164
|
+
ensure
|
165
|
+
@waiting -= 1
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
work = todo.shift
|
170
|
+
end
|
171
|
+
|
172
|
+
begin
|
173
|
+
@out_of_band_pending = true if block.call(work)
|
174
|
+
rescue Exception => e
|
175
|
+
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
@workers << th
|
181
|
+
|
182
|
+
th
|
183
|
+
end
|
184
|
+
|
185
|
+
private :spawn_thread
|
186
|
+
|
187
|
+
def trigger_before_thread_start_hooks
|
188
|
+
return unless @before_thread_start&.any?
|
189
|
+
|
190
|
+
@before_thread_start.each do |b|
|
191
|
+
begin
|
192
|
+
b[:block].call
|
193
|
+
rescue Exception => e
|
194
|
+
STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
nil
|
198
|
+
end
|
199
|
+
|
200
|
+
private :trigger_before_thread_start_hooks
|
201
|
+
|
202
|
+
def trigger_before_thread_exit_hooks
|
203
|
+
return unless @before_thread_exit&.any?
|
204
|
+
|
205
|
+
@before_thread_exit.each do |b|
|
206
|
+
begin
|
207
|
+
b[:block].call
|
208
|
+
rescue Exception => e
|
209
|
+
STDERR.puts "WARNING before_thread_exit hook failed with exception (#{e.class}) #{e.message}"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
nil
|
213
|
+
end
|
214
|
+
|
215
|
+
private :trigger_before_thread_exit_hooks
|
216
|
+
|
217
|
+
# @version 5.0.0
|
218
|
+
def trigger_out_of_band_hook
|
219
|
+
return false unless @out_of_band&.any?
|
220
|
+
|
221
|
+
# we execute on idle hook when all threads are free
|
222
|
+
return false unless @spawned == @waiting
|
223
|
+
@out_of_band_running = true
|
224
|
+
@out_of_band.each { |b| b[:block].call }
|
225
|
+
true
|
226
|
+
rescue Exception => e
|
227
|
+
STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
|
228
|
+
true
|
229
|
+
ensure
|
230
|
+
@out_of_band_running = false
|
231
|
+
@out_of_band_condvar.broadcast
|
232
|
+
end
|
233
|
+
|
234
|
+
private :trigger_out_of_band_hook
|
235
|
+
|
236
|
+
def wait_while_out_of_band_running
|
237
|
+
return unless @out_of_band_running
|
238
|
+
|
239
|
+
with_mutex do
|
240
|
+
@out_of_band_condvar.wait(@mutex) while @out_of_band_running
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# @version 5.0.0
|
245
|
+
def with_mutex(&block)
|
246
|
+
@mutex.owned? ?
|
247
|
+
yield :
|
248
|
+
@mutex.synchronize(&block)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Add +work+ to the todo list for a Thread to pickup and process.
|
252
|
+
def <<(work)
|
253
|
+
with_mutex do
|
254
|
+
if @shutdown
|
255
|
+
raise "Unable to add work while shutting down"
|
256
|
+
end
|
257
|
+
|
258
|
+
@todo << work
|
259
|
+
t = @todo.size
|
260
|
+
@backlog_max = t if t > @backlog_max
|
261
|
+
|
262
|
+
if @waiting < @todo.size and @spawned < @max
|
263
|
+
spawn_thread
|
264
|
+
end
|
265
|
+
|
266
|
+
@not_empty.signal
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# If there are any free threads in the pool, tell one to go ahead
|
271
|
+
# and exit. If +force+ is true, then a trim request is requested
|
272
|
+
# even if all threads are being utilized.
|
273
|
+
#
|
274
|
+
def trim(force=false)
|
275
|
+
with_mutex do
|
276
|
+
free = @waiting - @todo.size
|
277
|
+
if (force or free > 0) and @spawned - @trim_requested > @min
|
278
|
+
@trim_requested += 1
|
279
|
+
@not_empty.signal
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# If there are dead threads in the pool make them go away while decreasing
|
285
|
+
# spawned counter so that new healthy threads could be created again.
|
286
|
+
def reap
|
287
|
+
with_mutex do
|
288
|
+
dead_workers = @workers.reject(&:alive?)
|
289
|
+
|
290
|
+
dead_workers.each do |worker|
|
291
|
+
worker.kill
|
292
|
+
@spawned -= 1
|
293
|
+
end
|
294
|
+
|
295
|
+
@workers.delete_if do |w|
|
296
|
+
dead_workers.include?(w)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
class Automaton
|
302
|
+
def initialize(pool, timeout, thread_name, message)
|
303
|
+
@pool = pool
|
304
|
+
@timeout = timeout
|
305
|
+
@thread_name = thread_name
|
306
|
+
@message = message
|
307
|
+
@running = false
|
308
|
+
end
|
309
|
+
|
310
|
+
def start!
|
311
|
+
@running = true
|
312
|
+
|
313
|
+
@thread = Thread.new do
|
314
|
+
Puma.set_thread_name @thread_name
|
315
|
+
while @running
|
316
|
+
@pool.public_send(@message)
|
317
|
+
sleep @timeout
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def stop
|
323
|
+
@running = false
|
324
|
+
@thread.wakeup
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def auto_trim!(timeout=@auto_trim_time)
|
329
|
+
@auto_trim = Automaton.new(self, timeout, "#{@name} tp trim", :trim)
|
330
|
+
@auto_trim.start!
|
331
|
+
end
|
332
|
+
|
333
|
+
def auto_reap!(timeout=@reaping_time)
|
334
|
+
@reaper = Automaton.new(self, timeout, "#{@name} tp reap", :reap)
|
335
|
+
@reaper.start!
|
336
|
+
end
|
337
|
+
|
338
|
+
# Allows ThreadPool::ForceShutdown to be raised within the
|
339
|
+
# provided block if the thread is forced to shutdown during execution.
|
340
|
+
def with_force_shutdown
|
341
|
+
t = Thread.current
|
342
|
+
@shutdown_mutex.synchronize do
|
343
|
+
raise ForceShutdown if @force_shutdown
|
344
|
+
t[:with_force_shutdown] = true
|
345
|
+
end
|
346
|
+
yield
|
347
|
+
ensure
|
348
|
+
t[:with_force_shutdown] = false
|
349
|
+
end
|
350
|
+
|
351
|
+
# Tell all threads in the pool to exit and wait for them to finish.
|
352
|
+
# Wait +timeout+ seconds then raise +ForceShutdown+ in remaining threads.
|
353
|
+
# Next, wait an extra +@shutdown_grace_time+ seconds then force-kill remaining
|
354
|
+
# threads. Finally, wait 1 second for remaining threads to exit.
|
355
|
+
#
|
356
|
+
def shutdown(timeout=-1)
|
357
|
+
threads = with_mutex do
|
358
|
+
@shutdown = true
|
359
|
+
@trim_requested = @spawned
|
360
|
+
@not_empty.broadcast
|
361
|
+
@not_full.broadcast
|
362
|
+
|
363
|
+
@auto_trim&.stop
|
364
|
+
@reaper&.stop
|
365
|
+
# dup workers so that we join them all safely
|
366
|
+
@workers.dup
|
367
|
+
end
|
368
|
+
|
369
|
+
if timeout == -1
|
370
|
+
# Wait for threads to finish without force shutdown.
|
371
|
+
threads.each(&:join)
|
372
|
+
else
|
373
|
+
join = ->(inner_timeout) do
|
374
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
375
|
+
threads.reject! do |t|
|
376
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
377
|
+
t.join inner_timeout - elapsed
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# Wait +timeout+ seconds for threads to finish.
|
382
|
+
join.call(timeout)
|
383
|
+
|
384
|
+
# If threads are still running, raise ForceShutdown and wait to finish.
|
385
|
+
@shutdown_mutex.synchronize do
|
386
|
+
@force_shutdown = true
|
387
|
+
threads.each do |t|
|
388
|
+
t.raise ForceShutdown if t[:with_force_shutdown]
|
389
|
+
end
|
390
|
+
end
|
391
|
+
join.call(@shutdown_grace_time)
|
392
|
+
|
393
|
+
# If threads are _still_ running, forcefully kill them and wait to finish.
|
394
|
+
threads.each(&:kill)
|
395
|
+
join.call(1)
|
396
|
+
end
|
397
|
+
|
398
|
+
@spawned = 0
|
399
|
+
@workers = []
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
data/lib/puma/util.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uri/common'
|
4
|
+
|
5
|
+
module Puma
|
6
|
+
module Util
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def pipe
|
10
|
+
IO.pipe
|
11
|
+
end
|
12
|
+
|
13
|
+
# Escapes and unescapes a URI escaped string with
|
14
|
+
# +encoding+. +encoding+ will be the target encoding of the string
|
15
|
+
# returned, and it defaults to UTF-8
|
16
|
+
if defined?(::Encoding)
|
17
|
+
def escape(s, encoding = Encoding::UTF_8)
|
18
|
+
URI.encode_www_form_component(s, encoding)
|
19
|
+
end
|
20
|
+
|
21
|
+
def unescape(s, encoding = Encoding::UTF_8)
|
22
|
+
URI.decode_www_form_component(s, encoding)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
def escape(s, encoding = nil)
|
26
|
+
URI.encode_www_form_component(s, encoding)
|
27
|
+
end
|
28
|
+
|
29
|
+
def unescape(s, encoding = nil)
|
30
|
+
URI.decode_www_form_component(s, encoding)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
module_function :unescape, :escape
|
34
|
+
|
35
|
+
DEFAULT_SEP = /[&;] */n
|
36
|
+
|
37
|
+
# Stolen from Mongrel, with some small modifications:
|
38
|
+
# Parses a query string by breaking it up at the '&'
|
39
|
+
# and ';' characters. You can also use this to parse
|
40
|
+
# cookies by changing the characters used in the second
|
41
|
+
# parameter (which defaults to '&;').
|
42
|
+
def parse_query(qs, d = nil, &unescaper)
|
43
|
+
unescaper ||= method(:unescape)
|
44
|
+
|
45
|
+
params = {}
|
46
|
+
|
47
|
+
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
|
48
|
+
next if p.empty?
|
49
|
+
k, v = p.split('=', 2).map(&unescaper)
|
50
|
+
|
51
|
+
if cur = params[k]
|
52
|
+
if cur.class == Array
|
53
|
+
params[k] << v
|
54
|
+
else
|
55
|
+
params[k] = [cur, v]
|
56
|
+
end
|
57
|
+
else
|
58
|
+
params[k] = v
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
params
|
63
|
+
end
|
64
|
+
|
65
|
+
# A case-insensitive Hash that preserves the original case of a
|
66
|
+
# header when set.
|
67
|
+
class HeaderHash < Hash
|
68
|
+
def self.new(hash={})
|
69
|
+
HeaderHash === hash ? hash : super(hash)
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(hash={})
|
73
|
+
super()
|
74
|
+
@names = {}
|
75
|
+
hash.each { |k, v| self[k] = v }
|
76
|
+
end
|
77
|
+
|
78
|
+
def each
|
79
|
+
super do |k, v|
|
80
|
+
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# @!attribute [r] to_hash
|
85
|
+
def to_hash
|
86
|
+
hash = {}
|
87
|
+
each { |k,v| hash[k] = v }
|
88
|
+
hash
|
89
|
+
end
|
90
|
+
|
91
|
+
def [](k)
|
92
|
+
super(k) || super(@names[k.downcase])
|
93
|
+
end
|
94
|
+
|
95
|
+
def []=(k, v)
|
96
|
+
canonical = k.downcase
|
97
|
+
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
|
98
|
+
@names[k] = @names[canonical] = k
|
99
|
+
super k, v
|
100
|
+
end
|
101
|
+
|
102
|
+
def delete(k)
|
103
|
+
canonical = k.downcase
|
104
|
+
result = super @names.delete(canonical)
|
105
|
+
@names.delete_if { |name,| name.downcase == canonical }
|
106
|
+
result
|
107
|
+
end
|
108
|
+
|
109
|
+
def include?(k)
|
110
|
+
@names.include?(k) || @names.include?(k.downcase)
|
111
|
+
end
|
112
|
+
|
113
|
+
alias_method :has_key?, :include?
|
114
|
+
alias_method :member?, :include?
|
115
|
+
alias_method :key?, :include?
|
116
|
+
|
117
|
+
def merge!(other)
|
118
|
+
other.each { |k, v| self[k] = v }
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
def merge(other)
|
123
|
+
hash = dup
|
124
|
+
hash.merge! other
|
125
|
+
end
|
126
|
+
|
127
|
+
def replace(other)
|
128
|
+
clear
|
129
|
+
other.each { |k, v| self[k] = v }
|
130
|
+
self
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|