puma 4.3.12 → 5.6.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +1511 -524
- data/LICENSE +23 -20
- data/README.md +120 -36
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +63 -26
- data/docs/compile_options.md +21 -0
- data/docs/deployment.md +60 -69
- data/docs/fork_worker.md +33 -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/jungle/README.md +9 -0
- data/{tools → docs}/jungle/rc.d/README.md +1 -1
- data/{tools → docs}/jungle/rc.d/puma +2 -2
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +46 -23
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +85 -128
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +44 -10
- data/ext/puma_http11/http11_parser.c +45 -47
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +1 -1
- data/ext/puma_http11/http11_parser.rl +1 -1
- data/ext/puma_http11/http11_parser_common.rl +0 -0
- data/ext/puma_http11/mini_ssl.c +225 -89
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +3 -5
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +109 -67
- data/ext/puma_http11/puma_http11.c +32 -51
- data/lib/puma/app/status.rb +50 -36
- data/lib/puma/binder.rb +225 -106
- data/lib/puma/cli.rb +24 -18
- data/lib/puma/client.rb +104 -76
- data/lib/puma/cluster/worker.rb +173 -0
- data/lib/puma/cluster/worker_handle.rb +94 -0
- data/lib/puma/cluster.rb +212 -220
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +58 -49
- data/lib/puma/const.rb +13 -6
- data/lib/puma/control_cli.rb +99 -76
- data/lib/puma/detect.rb +29 -2
- data/lib/puma/dsl.rb +368 -96
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +55 -34
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher.rb +128 -46
- data/lib/puma/minissl/context_builder.rb +14 -9
- data/lib/puma/minissl.rb +137 -50
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/tmp_restart.rb +0 -0
- data/lib/puma/plugin.rb +3 -12
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +1 -5
- data/lib/puma/rack/urlmap.rb +0 -0
- data/lib/puma/rack_default.rb +0 -0
- data/lib/puma/reactor.rb +85 -369
- data/lib/puma/request.rb +476 -0
- data/lib/puma/runner.rb +46 -61
- data/lib/puma/server.rb +292 -763
- data/lib/puma/single.rb +9 -65
- data/lib/puma/state_file.rb +48 -8
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +125 -57
- data/lib/puma/util.rb +32 -4
- data/lib/puma.rb +48 -0
- data/lib/rack/handler/puma.rb +2 -3
- data/lib/rack/version_restriction.rb +15 -0
- data/tools/{docker/Dockerfile → Dockerfile} +1 -1
- data/tools/trickletest.rb +0 -0
- metadata +28 -23
- data/docs/tcp_mode.md +0 -96
- data/ext/puma_http11/io_buffer.c +0 -155
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
- data/lib/puma/accept_nonblock.rb +0 -29
- data/lib/puma/tcp_logger.rb +0 -41
- data/tools/jungle/README.md +0 -19
- data/tools/jungle/init.d/README.md +0 -61
- data/tools/jungle/init.d/puma +0 -421
- data/tools/jungle/init.d/run-puma +0 -18
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/client.rb
CHANGED
@@ -72,6 +72,7 @@ module Puma
|
|
72
72
|
@parser = HttpParser.new
|
73
73
|
@parsed_bytes = 0
|
74
74
|
@read_header = true
|
75
|
+
@read_proxy = false
|
75
76
|
@ready = false
|
76
77
|
|
77
78
|
@body = nil
|
@@ -85,7 +86,9 @@ module Puma
|
|
85
86
|
@hijacked = false
|
86
87
|
|
87
88
|
@peerip = nil
|
89
|
+
@listener = nil
|
88
90
|
@remote_addr_header = nil
|
91
|
+
@expect_proxy_proto = false
|
89
92
|
|
90
93
|
@body_remain = 0
|
91
94
|
|
@@ -97,10 +100,17 @@ module Puma
|
|
97
100
|
|
98
101
|
attr_writer :peerip
|
99
102
|
|
100
|
-
attr_accessor :remote_addr_header
|
103
|
+
attr_accessor :remote_addr_header, :listener
|
101
104
|
|
102
105
|
def_delegators :@io, :closed?
|
103
106
|
|
107
|
+
# Test to see if io meets a bare minimum of functioning, @to_io needs to be
|
108
|
+
# used for MiniSSL::Socket
|
109
|
+
def io_ok?
|
110
|
+
@to_io.is_a?(::BasicSocket) && !closed?
|
111
|
+
end
|
112
|
+
|
113
|
+
# @!attribute [r] inspect
|
104
114
|
def inspect
|
105
115
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
106
116
|
end
|
@@ -112,27 +122,36 @@ module Puma
|
|
112
122
|
env[HIJACK_IO] ||= @io
|
113
123
|
end
|
114
124
|
|
125
|
+
# @!attribute [r] in_data_phase
|
115
126
|
def in_data_phase
|
116
|
-
|
127
|
+
!(@read_header || @read_proxy)
|
117
128
|
end
|
118
129
|
|
119
130
|
def set_timeout(val)
|
120
|
-
@timeout_at =
|
131
|
+
@timeout_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + val
|
132
|
+
end
|
133
|
+
|
134
|
+
# Number of seconds until the timeout elapses.
|
135
|
+
def timeout
|
136
|
+
[@timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
|
121
137
|
end
|
122
138
|
|
123
139
|
def reset(fast_check=true)
|
124
140
|
@parser.reset
|
125
141
|
@read_header = true
|
142
|
+
@read_proxy = !!@expect_proxy_proto
|
126
143
|
@env = @proto_env.dup
|
127
144
|
@body = nil
|
128
145
|
@tempfile = nil
|
129
146
|
@parsed_bytes = 0
|
130
147
|
@ready = false
|
131
148
|
@body_remain = 0
|
132
|
-
@peerip = nil
|
149
|
+
@peerip = nil if @remote_addr_header
|
133
150
|
@in_last_chunk = false
|
134
151
|
|
135
152
|
if @buffer
|
153
|
+
return false unless try_to_parse_proxy_protocol
|
154
|
+
|
136
155
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
137
156
|
|
138
157
|
if @parser.finished?
|
@@ -145,8 +164,7 @@ module Puma
|
|
145
164
|
return false
|
146
165
|
else
|
147
166
|
begin
|
148
|
-
if fast_check &&
|
149
|
-
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
167
|
+
if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
|
150
168
|
return try_to_finish
|
151
169
|
end
|
152
170
|
rescue IOError
|
@@ -159,19 +177,45 @@ module Puma
|
|
159
177
|
def close
|
160
178
|
begin
|
161
179
|
@io.close
|
162
|
-
rescue IOError
|
163
|
-
|
180
|
+
rescue IOError, Errno::EBADF
|
181
|
+
Puma::Util.purge_interrupt_queue
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# If necessary, read the PROXY protocol from the buffer. Returns
|
186
|
+
# false if more data is needed.
|
187
|
+
def try_to_parse_proxy_protocol
|
188
|
+
if @read_proxy
|
189
|
+
if @expect_proxy_proto == :v1
|
190
|
+
if @buffer.include? "\r\n"
|
191
|
+
if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
|
192
|
+
if md[1]
|
193
|
+
@peerip = md[1].split(" ")[0]
|
194
|
+
end
|
195
|
+
@buffer = md.post_match
|
196
|
+
end
|
197
|
+
# if the buffer has a \r\n but doesn't have a PROXY protocol
|
198
|
+
# request, this is just HTTP from a non-PROXY client; move on
|
199
|
+
@read_proxy = false
|
200
|
+
return @buffer.size > 0
|
201
|
+
else
|
202
|
+
return false
|
203
|
+
end
|
204
|
+
end
|
164
205
|
end
|
206
|
+
true
|
165
207
|
end
|
166
208
|
|
167
209
|
def try_to_finish
|
168
|
-
return read_body
|
210
|
+
return read_body if in_data_phase
|
169
211
|
|
170
212
|
begin
|
171
213
|
data = @io.read_nonblock(CHUNK_SIZE)
|
172
214
|
rescue IO::WaitReadable
|
173
215
|
return false
|
174
|
-
rescue
|
216
|
+
rescue EOFError
|
217
|
+
# Swallow error, don't log
|
218
|
+
rescue SystemCallError, IOError
|
175
219
|
raise ConnectionError, "Connection error detected during read"
|
176
220
|
end
|
177
221
|
|
@@ -188,6 +232,8 @@ module Puma
|
|
188
232
|
@buffer = data
|
189
233
|
end
|
190
234
|
|
235
|
+
return false unless try_to_parse_proxy_protocol
|
236
|
+
|
191
237
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
192
238
|
|
193
239
|
if @parser.finished?
|
@@ -200,68 +246,20 @@ module Puma
|
|
200
246
|
false
|
201
247
|
end
|
202
248
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
data = @io.sysread_nonblock(CHUNK_SIZE)
|
209
|
-
rescue OpenSSL::SSL::SSLError => e
|
210
|
-
return false if e.kind_of? IO::WaitReadable
|
211
|
-
raise e
|
212
|
-
end
|
213
|
-
|
214
|
-
# No data means a closed socket
|
215
|
-
unless data
|
216
|
-
@buffer = nil
|
217
|
-
set_ready
|
218
|
-
raise EOFError
|
219
|
-
end
|
220
|
-
|
221
|
-
if @buffer
|
222
|
-
@buffer << data
|
223
|
-
else
|
224
|
-
@buffer = data
|
225
|
-
end
|
226
|
-
|
227
|
-
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
228
|
-
|
229
|
-
if @parser.finished?
|
230
|
-
return setup_body
|
231
|
-
elsif @parsed_bytes >= MAX_HEADER
|
232
|
-
raise HttpParserError,
|
233
|
-
"HEADER is longer than allowed, aborting client early."
|
234
|
-
end
|
235
|
-
|
236
|
-
false
|
237
|
-
end
|
238
|
-
|
239
|
-
def eagerly_finish
|
240
|
-
return true if @ready
|
241
|
-
|
242
|
-
if @io.kind_of? OpenSSL::SSL::SSLSocket
|
243
|
-
return true if jruby_start_try_to_finish
|
244
|
-
end
|
245
|
-
|
246
|
-
return false unless IO.select([@to_io], nil, nil, 0)
|
247
|
-
try_to_finish
|
248
|
-
end
|
249
|
-
|
250
|
-
else
|
249
|
+
def eagerly_finish
|
250
|
+
return true if @ready
|
251
|
+
return false unless @to_io.wait_readable(0)
|
252
|
+
try_to_finish
|
253
|
+
end
|
251
254
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
end
|
257
|
-
end # IS_JRUBY
|
255
|
+
def finish(timeout)
|
256
|
+
return if @ready
|
257
|
+
@to_io.wait_readable(timeout) || timeout! until try_to_finish
|
258
|
+
end
|
258
259
|
|
259
|
-
def
|
260
|
-
|
261
|
-
|
262
|
-
IO.select([@to_io], nil, nil)
|
263
|
-
end
|
264
|
-
true
|
260
|
+
def timeout!
|
261
|
+
write_error(408) if in_data_phase
|
262
|
+
raise ConnectionError
|
265
263
|
end
|
266
264
|
|
267
265
|
def write_error(status_code)
|
@@ -275,7 +273,7 @@ module Puma
|
|
275
273
|
return @peerip if @peerip
|
276
274
|
|
277
275
|
if @remote_addr_header
|
278
|
-
hdr = (@env[@remote_addr_header] ||
|
276
|
+
hdr = (@env[@remote_addr_header] || LOCALHOST_IP).split(/[\s,]/).first
|
279
277
|
@peerip = hdr
|
280
278
|
return hdr
|
281
279
|
end
|
@@ -283,6 +281,26 @@ module Puma
|
|
283
281
|
@peerip ||= @io.peeraddr.last
|
284
282
|
end
|
285
283
|
|
284
|
+
# Returns true if the persistent connection can be closed immediately
|
285
|
+
# without waiting for the configured idle/shutdown timeout.
|
286
|
+
# @version 5.0.0
|
287
|
+
#
|
288
|
+
def can_close?
|
289
|
+
# Allow connection to close if we're not in the middle of parsing a request.
|
290
|
+
@parsed_bytes == 0
|
291
|
+
end
|
292
|
+
|
293
|
+
def expect_proxy_proto=(val)
|
294
|
+
if val
|
295
|
+
if @read_header
|
296
|
+
@read_proxy = true
|
297
|
+
end
|
298
|
+
else
|
299
|
+
@read_proxy = false
|
300
|
+
end
|
301
|
+
@expect_proxy_proto = val
|
302
|
+
end
|
303
|
+
|
286
304
|
private
|
287
305
|
|
288
306
|
def setup_body
|
@@ -351,6 +369,7 @@ module Puma
|
|
351
369
|
|
352
370
|
if remain > MAX_BODY
|
353
371
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
372
|
+
@body.unlink
|
354
373
|
@body.binmode
|
355
374
|
@tempfile = @body
|
356
375
|
else
|
@@ -363,7 +382,7 @@ module Puma
|
|
363
382
|
|
364
383
|
@body_remain = remain
|
365
384
|
|
366
|
-
|
385
|
+
false
|
367
386
|
end
|
368
387
|
|
369
388
|
def read_body
|
@@ -430,7 +449,7 @@ module Puma
|
|
430
449
|
end
|
431
450
|
|
432
451
|
if decode_chunk(chunk)
|
433
|
-
@env[CONTENT_LENGTH] = @chunked_content_length
|
452
|
+
@env[CONTENT_LENGTH] = @chunked_content_length.to_s
|
434
453
|
return true
|
435
454
|
end
|
436
455
|
end
|
@@ -442,17 +461,18 @@ module Puma
|
|
442
461
|
@prev_chunk = ""
|
443
462
|
|
444
463
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
464
|
+
@body.unlink
|
445
465
|
@body.binmode
|
446
466
|
@tempfile = @body
|
447
|
-
|
448
467
|
@chunked_content_length = 0
|
449
468
|
|
450
469
|
if decode_chunk(body)
|
451
|
-
@env[CONTENT_LENGTH] = @chunked_content_length
|
470
|
+
@env[CONTENT_LENGTH] = @chunked_content_length.to_s
|
452
471
|
return true
|
453
472
|
end
|
454
473
|
end
|
455
474
|
|
475
|
+
# @version 5.0.0
|
456
476
|
def write_chunk(str)
|
457
477
|
@chunked_content_length += @body.write(str)
|
458
478
|
end
|
@@ -466,7 +486,15 @@ module Puma
|
|
466
486
|
chunk = chunk[@partial_part_left..-1]
|
467
487
|
@partial_part_left = 0
|
468
488
|
else
|
469
|
-
|
489
|
+
if @partial_part_left > 2
|
490
|
+
if @partial_part_left == chunk.size + 1
|
491
|
+
# Don't include the last \r
|
492
|
+
write_chunk(chunk[0..(@partial_part_left-3)])
|
493
|
+
else
|
494
|
+
# don't include the last \r\n
|
495
|
+
write_chunk(chunk)
|
496
|
+
end
|
497
|
+
end
|
470
498
|
@partial_part_left -= chunk.size
|
471
499
|
return false
|
472
500
|
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Puma
|
4
|
+
class Cluster < Puma::Runner
|
5
|
+
# This class is instantiated by the `Puma::Cluster` and represents a single
|
6
|
+
# worker process.
|
7
|
+
#
|
8
|
+
# At the core of this class is running an instance of `Puma::Server` which
|
9
|
+
# gets created via the `start_server` method from the `Puma::Runner` class
|
10
|
+
# that this inherits from.
|
11
|
+
class Worker < Puma::Runner
|
12
|
+
attr_reader :index, :master
|
13
|
+
|
14
|
+
def initialize(index:, master:, launcher:, pipes:, server: nil)
|
15
|
+
super launcher, launcher.events
|
16
|
+
|
17
|
+
@index = index
|
18
|
+
@master = master
|
19
|
+
@launcher = launcher
|
20
|
+
@options = launcher.options
|
21
|
+
@check_pipe = pipes[:check_pipe]
|
22
|
+
@worker_write = pipes[:worker_write]
|
23
|
+
@fork_pipe = pipes[:fork_pipe]
|
24
|
+
@wakeup = pipes[:wakeup]
|
25
|
+
@server = server
|
26
|
+
end
|
27
|
+
|
28
|
+
def run
|
29
|
+
title = "puma: cluster worker #{index}: #{master}"
|
30
|
+
title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
31
|
+
$0 = title
|
32
|
+
|
33
|
+
Signal.trap "SIGINT", "IGNORE"
|
34
|
+
Signal.trap "SIGCHLD", "DEFAULT"
|
35
|
+
|
36
|
+
Thread.new do
|
37
|
+
Puma.set_thread_name "wrkr check"
|
38
|
+
@check_pipe.wait_readable
|
39
|
+
log "! Detected parent died, dying"
|
40
|
+
exit! 1
|
41
|
+
end
|
42
|
+
|
43
|
+
# If we're not running under a Bundler context, then
|
44
|
+
# report the info about the context we will be using
|
45
|
+
if !ENV['BUNDLE_GEMFILE']
|
46
|
+
if File.exist?("Gemfile")
|
47
|
+
log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
|
48
|
+
elsif File.exist?("gems.rb")
|
49
|
+
log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Invoke any worker boot hooks so they can get
|
54
|
+
# things in shape before booting the app.
|
55
|
+
@launcher.config.run_hooks :before_worker_boot, index, @launcher.events
|
56
|
+
|
57
|
+
begin
|
58
|
+
server = @server ||= start_server
|
59
|
+
rescue Exception => e
|
60
|
+
log "! Unable to start worker"
|
61
|
+
log e.backtrace[0]
|
62
|
+
exit 1
|
63
|
+
end
|
64
|
+
|
65
|
+
restart_server = Queue.new << true << false
|
66
|
+
|
67
|
+
fork_worker = @options[:fork_worker] && index == 0
|
68
|
+
|
69
|
+
if fork_worker
|
70
|
+
restart_server.clear
|
71
|
+
worker_pids = []
|
72
|
+
Signal.trap "SIGCHLD" do
|
73
|
+
wakeup! if worker_pids.reject! do |p|
|
74
|
+
Process.wait(p, Process::WNOHANG) rescue true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
Thread.new do
|
79
|
+
Puma.set_thread_name "wrkr fork"
|
80
|
+
while (idx = @fork_pipe.gets)
|
81
|
+
idx = idx.to_i
|
82
|
+
if idx == -1 # stop server
|
83
|
+
if restart_server.length > 0
|
84
|
+
restart_server.clear
|
85
|
+
server.begin_restart(true)
|
86
|
+
@launcher.config.run_hooks :before_refork, nil, @launcher.events
|
87
|
+
Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
|
88
|
+
end
|
89
|
+
elsif idx == 0 # restart server
|
90
|
+
restart_server << true << false
|
91
|
+
else # fork worker
|
92
|
+
worker_pids << pid = spawn_worker(idx)
|
93
|
+
@worker_write << "f#{pid}:#{idx}\n" rescue nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
Signal.trap "SIGTERM" do
|
100
|
+
@worker_write << "e#{Process.pid}\n" rescue nil
|
101
|
+
restart_server.clear
|
102
|
+
server.stop
|
103
|
+
restart_server << false
|
104
|
+
end
|
105
|
+
|
106
|
+
begin
|
107
|
+
@worker_write << "b#{Process.pid}:#{index}\n"
|
108
|
+
rescue SystemCallError, IOError
|
109
|
+
Puma::Util.purge_interrupt_queue
|
110
|
+
STDERR.puts "Master seems to have exited, exiting."
|
111
|
+
return
|
112
|
+
end
|
113
|
+
|
114
|
+
while restart_server.pop
|
115
|
+
server_thread = server.run
|
116
|
+
stat_thread ||= Thread.new(@worker_write) do |io|
|
117
|
+
Puma.set_thread_name "stat pld"
|
118
|
+
base_payload = "p#{Process.pid}"
|
119
|
+
|
120
|
+
while true
|
121
|
+
begin
|
122
|
+
b = server.backlog || 0
|
123
|
+
r = server.running || 0
|
124
|
+
t = server.pool_capacity || 0
|
125
|
+
m = server.max_threads || 0
|
126
|
+
rc = server.requests_count || 0
|
127
|
+
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
|
128
|
+
io << payload
|
129
|
+
rescue IOError
|
130
|
+
Puma::Util.purge_interrupt_queue
|
131
|
+
break
|
132
|
+
end
|
133
|
+
sleep @options[:worker_check_interval]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
server_thread.join
|
137
|
+
end
|
138
|
+
|
139
|
+
# Invoke any worker shutdown hooks so they can prevent the worker
|
140
|
+
# exiting until any background operations are completed
|
141
|
+
@launcher.config.run_hooks :before_worker_shutdown, index, @launcher.events
|
142
|
+
ensure
|
143
|
+
@worker_write << "t#{Process.pid}\n" rescue nil
|
144
|
+
@worker_write.close
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def spawn_worker(idx)
|
150
|
+
@launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
|
151
|
+
|
152
|
+
pid = fork do
|
153
|
+
new_worker = Worker.new index: idx,
|
154
|
+
master: master,
|
155
|
+
launcher: @launcher,
|
156
|
+
pipes: { check_pipe: @check_pipe,
|
157
|
+
worker_write: @worker_write },
|
158
|
+
server: @server
|
159
|
+
new_worker.run
|
160
|
+
end
|
161
|
+
|
162
|
+
if !pid
|
163
|
+
log "! Complete inability to spawn new workers detected"
|
164
|
+
log "! Seppuku is the only choice."
|
165
|
+
exit! 1
|
166
|
+
end
|
167
|
+
|
168
|
+
@launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
|
169
|
+
pid
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Puma
|
4
|
+
class Cluster < Runner
|
5
|
+
# This class represents a worker process from the perspective of the puma
|
6
|
+
# master process. It contains information about the process and its health
|
7
|
+
# and it exposes methods to control the process via IPC. It does not
|
8
|
+
# include the actual logic executed by the worker process itself. For that,
|
9
|
+
# see Puma::Cluster::Worker.
|
10
|
+
class WorkerHandle
|
11
|
+
def initialize(idx, pid, phase, options)
|
12
|
+
@index = idx
|
13
|
+
@pid = pid
|
14
|
+
@phase = phase
|
15
|
+
@stage = :started
|
16
|
+
@signal = "TERM"
|
17
|
+
@options = options
|
18
|
+
@first_term_sent = nil
|
19
|
+
@started_at = Time.now
|
20
|
+
@last_checkin = Time.now
|
21
|
+
@last_status = {}
|
22
|
+
@term = false
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
26
|
+
|
27
|
+
# @version 5.0.0
|
28
|
+
attr_writer :pid, :phase
|
29
|
+
|
30
|
+
def booted?
|
31
|
+
@stage == :booted
|
32
|
+
end
|
33
|
+
|
34
|
+
def uptime
|
35
|
+
Time.now - started_at
|
36
|
+
end
|
37
|
+
|
38
|
+
def boot!
|
39
|
+
@last_checkin = Time.now
|
40
|
+
@stage = :booted
|
41
|
+
end
|
42
|
+
|
43
|
+
def term!
|
44
|
+
@term = true
|
45
|
+
end
|
46
|
+
|
47
|
+
def term?
|
48
|
+
@term
|
49
|
+
end
|
50
|
+
|
51
|
+
def ping!(status)
|
52
|
+
@last_checkin = Time.now
|
53
|
+
captures = status.match(/{ "backlog":(?<backlog>\d*), "running":(?<running>\d*), "pool_capacity":(?<pool_capacity>\d*), "max_threads": (?<max_threads>\d*), "requests_count": (?<requests_count>\d*) }/)
|
54
|
+
@last_status = captures.names.inject({}) do |hash, key|
|
55
|
+
hash[key.to_sym] = captures[key].to_i
|
56
|
+
hash
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# @see Puma::Cluster#check_workers
|
61
|
+
# @version 5.0.0
|
62
|
+
def ping_timeout
|
63
|
+
@last_checkin +
|
64
|
+
(booted? ?
|
65
|
+
@options[:worker_timeout] :
|
66
|
+
@options[:worker_boot_timeout]
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def term
|
71
|
+
begin
|
72
|
+
if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
|
73
|
+
@signal = "KILL"
|
74
|
+
else
|
75
|
+
@term ||= true
|
76
|
+
@first_term_sent ||= Time.now
|
77
|
+
end
|
78
|
+
Process.kill @signal, @pid if @pid
|
79
|
+
rescue Errno::ESRCH
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def kill
|
84
|
+
@signal = 'KILL'
|
85
|
+
term
|
86
|
+
end
|
87
|
+
|
88
|
+
def hup
|
89
|
+
Process.kill "HUP", @pid
|
90
|
+
rescue Errno::ESRCH
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|