puma 5.2.2 → 6.3.0
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 +483 -4
- data/README.md +101 -20
- data/bin/puma-wild +1 -1
- data/docs/architecture.md +50 -16
- data/docs/compile_options.md +38 -2
- data/docs/deployment.md +53 -67
- data/docs/fork_worker.md +1 -3
- data/docs/jungle/rc.d/README.md +1 -1
- data/docs/kubernetes.md +1 -1
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +2 -3
- data/docs/restart.md +7 -7
- data/docs/signals.md +11 -10
- data/docs/stats.md +8 -8
- data/docs/systemd.md +65 -69
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/extconf.rb +44 -13
- data/ext/puma_http11/http11_parser.c +24 -11
- data/ext/puma_http11/http11_parser.h +2 -2
- data/ext/puma_http11/http11_parser.java.rl +2 -2
- data/ext/puma_http11/http11_parser.rl +2 -2
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +150 -23
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +50 -48
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +188 -102
- data/ext/puma_http11/puma_http11.c +18 -10
- data/lib/puma/app/status.rb +10 -7
- data/lib/puma/binder.rb +112 -62
- data/lib/puma/cli.rb +24 -20
- data/lib/puma/client.rb +162 -36
- data/lib/puma/cluster/worker.rb +31 -27
- data/lib/puma/cluster/worker_handle.rb +12 -1
- data/lib/puma/cluster.rb +102 -61
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +78 -54
- data/lib/puma/const.rb +135 -97
- data/lib/puma/control_cli.rb +25 -20
- data/lib/puma/detect.rb +12 -2
- data/lib/puma/dsl.rb +308 -58
- data/lib/puma/error_logger.rb +20 -11
- data/lib/puma/events.rb +6 -126
- data/lib/puma/io_buffer.rb +39 -4
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/{json.rb → json_serialization.rb} +1 -1
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +114 -173
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +30 -16
- data/lib/puma/minissl.rb +132 -38
- data/lib/puma/null_io.rb +5 -0
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +2 -2
- data/lib/puma/rack/builder.rb +7 -7
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +19 -10
- data/lib/puma/request.rb +373 -153
- data/lib/puma/runner.rb +74 -28
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +127 -136
- data/lib/puma/single.rb +13 -11
- data/lib/puma/state_file.rb +39 -7
- data/lib/puma/thread_pool.rb +33 -26
- data/lib/puma/util.rb +20 -15
- data/lib/puma.rb +28 -11
- data/lib/rack/handler/puma.rb +113 -86
- data/tools/Dockerfile +1 -1
- metadata +15 -10
- data/lib/puma/queue_close.rb +0 -26
- data/lib/puma/systemd.rb +0 -46
data/lib/puma/client.rb
CHANGED
@@ -8,7 +8,8 @@ class IO
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
require_relative 'detect'
|
12
|
+
require_relative 'io_buffer'
|
12
13
|
require 'tempfile'
|
13
14
|
require 'forwardable'
|
14
15
|
|
@@ -23,6 +24,11 @@ module Puma
|
|
23
24
|
|
24
25
|
class ConnectionError < RuntimeError; end
|
25
26
|
|
27
|
+
class HttpParserError501 < IOError; end
|
28
|
+
|
29
|
+
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
30
|
+
|
31
|
+
|
26
32
|
# An instance of this class represents a unique request from a client.
|
27
33
|
# For example, this could be a web request from a browser or from CURL.
|
28
34
|
#
|
@@ -35,7 +41,21 @@ module Puma
|
|
35
41
|
# Instances of this class are responsible for knowing if
|
36
42
|
# the header and body are fully buffered via the `try_to_finish` method.
|
37
43
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
38
|
-
|
44
|
+
#
|
45
|
+
class Client # :nodoc:
|
46
|
+
|
47
|
+
# this tests all values but the last, which must be chunked
|
48
|
+
ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
|
49
|
+
|
50
|
+
# chunked body validation
|
51
|
+
CHUNK_SIZE_INVALID = /[^\h]/.freeze
|
52
|
+
CHUNK_VALID_ENDING = "\r\n".freeze
|
53
|
+
|
54
|
+
# Content-Length header value validation
|
55
|
+
CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
|
56
|
+
|
57
|
+
TE_ERR_MSG = 'Invalid Transfer-Encoding'
|
58
|
+
|
39
59
|
# The object used for a request with no body. All requests with
|
40
60
|
# no body share this one object since it has no state.
|
41
61
|
EmptyBody = NullIO.new
|
@@ -46,16 +66,14 @@ module Puma
|
|
46
66
|
def initialize(io, env=nil)
|
47
67
|
@io = io
|
48
68
|
@to_io = io.to_io
|
69
|
+
@io_buffer = IOBuffer.new
|
49
70
|
@proto_env = env
|
50
|
-
|
51
|
-
@env = nil
|
52
|
-
else
|
53
|
-
@env = env.dup
|
54
|
-
end
|
71
|
+
@env = env&.dup
|
55
72
|
|
56
73
|
@parser = HttpParser.new
|
57
74
|
@parsed_bytes = 0
|
58
75
|
@read_header = true
|
76
|
+
@read_proxy = false
|
59
77
|
@ready = false
|
60
78
|
|
61
79
|
@body = nil
|
@@ -68,20 +86,29 @@ module Puma
|
|
68
86
|
@requests_served = 0
|
69
87
|
@hijacked = false
|
70
88
|
|
89
|
+
@http_content_length_limit = nil
|
90
|
+
@http_content_length_limit_exceeded = false
|
91
|
+
|
71
92
|
@peerip = nil
|
93
|
+
@peer_family = nil
|
94
|
+
@listener = nil
|
72
95
|
@remote_addr_header = nil
|
96
|
+
@expect_proxy_proto = false
|
73
97
|
|
74
98
|
@body_remain = 0
|
75
99
|
|
76
100
|
@in_last_chunk = false
|
101
|
+
|
102
|
+
# need unfrozen ASCII-8BIT, +'' is UTF-8
|
103
|
+
@read_buffer = String.new # rubocop: disable Performance/UnfreezeString
|
77
104
|
end
|
78
105
|
|
79
106
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
80
|
-
:tempfile
|
107
|
+
:tempfile, :io_buffer, :http_content_length_limit_exceeded
|
81
108
|
|
82
|
-
attr_writer :peerip
|
109
|
+
attr_writer :peerip, :http_content_length_limit
|
83
110
|
|
84
|
-
attr_accessor :remote_addr_header
|
111
|
+
attr_accessor :remote_addr_header, :listener
|
85
112
|
|
86
113
|
def_delegators :@io, :closed?
|
87
114
|
|
@@ -105,7 +132,7 @@ module Puma
|
|
105
132
|
|
106
133
|
# @!attribute [r] in_data_phase
|
107
134
|
def in_data_phase
|
108
|
-
|
135
|
+
!(@read_header || @read_proxy)
|
109
136
|
end
|
110
137
|
|
111
138
|
def set_timeout(val)
|
@@ -119,17 +146,22 @@ module Puma
|
|
119
146
|
|
120
147
|
def reset(fast_check=true)
|
121
148
|
@parser.reset
|
149
|
+
@io_buffer.reset
|
122
150
|
@read_header = true
|
151
|
+
@read_proxy = !!@expect_proxy_proto
|
123
152
|
@env = @proto_env.dup
|
124
153
|
@body = nil
|
125
154
|
@tempfile = nil
|
126
155
|
@parsed_bytes = 0
|
127
156
|
@ready = false
|
128
157
|
@body_remain = 0
|
129
|
-
@peerip = nil
|
158
|
+
@peerip = nil if @remote_addr_header
|
130
159
|
@in_last_chunk = false
|
160
|
+
@http_content_length_limit_exceeded = false
|
131
161
|
|
132
162
|
if @buffer
|
163
|
+
return false unless try_to_parse_proxy_protocol
|
164
|
+
|
133
165
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
134
166
|
|
135
167
|
if @parser.finished?
|
@@ -142,8 +174,7 @@ module Puma
|
|
142
174
|
return false
|
143
175
|
else
|
144
176
|
begin
|
145
|
-
if fast_check &&
|
146
|
-
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
177
|
+
if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
|
147
178
|
return try_to_finish
|
148
179
|
end
|
149
180
|
rescue IOError
|
@@ -156,13 +187,48 @@ module Puma
|
|
156
187
|
def close
|
157
188
|
begin
|
158
189
|
@io.close
|
159
|
-
rescue IOError
|
160
|
-
|
190
|
+
rescue IOError, Errno::EBADF
|
191
|
+
Puma::Util.purge_interrupt_queue
|
161
192
|
end
|
162
193
|
end
|
163
194
|
|
195
|
+
# If necessary, read the PROXY protocol from the buffer. Returns
|
196
|
+
# false if more data is needed.
|
197
|
+
def try_to_parse_proxy_protocol
|
198
|
+
if @read_proxy
|
199
|
+
if @expect_proxy_proto == :v1
|
200
|
+
if @buffer.include? "\r\n"
|
201
|
+
if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
|
202
|
+
if md[1]
|
203
|
+
@peerip = md[1].split(" ")[0]
|
204
|
+
end
|
205
|
+
@buffer = md.post_match
|
206
|
+
end
|
207
|
+
# if the buffer has a \r\n but doesn't have a PROXY protocol
|
208
|
+
# request, this is just HTTP from a non-PROXY client; move on
|
209
|
+
@read_proxy = false
|
210
|
+
return @buffer.size > 0
|
211
|
+
else
|
212
|
+
return false
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
true
|
217
|
+
end
|
218
|
+
|
164
219
|
def try_to_finish
|
165
|
-
|
220
|
+
if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
|
221
|
+
@http_content_length_limit_exceeded = true
|
222
|
+
end
|
223
|
+
|
224
|
+
if @http_content_length_limit_exceeded
|
225
|
+
@buffer = nil
|
226
|
+
@body = EmptyBody
|
227
|
+
set_ready
|
228
|
+
return true
|
229
|
+
end
|
230
|
+
|
231
|
+
return read_body if in_data_phase
|
166
232
|
|
167
233
|
begin
|
168
234
|
data = @io.read_nonblock(CHUNK_SIZE)
|
@@ -187,8 +253,14 @@ module Puma
|
|
187
253
|
@buffer = data
|
188
254
|
end
|
189
255
|
|
256
|
+
return false unless try_to_parse_proxy_protocol
|
257
|
+
|
190
258
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
191
259
|
|
260
|
+
if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
|
261
|
+
@http_content_length_limit_exceeded = true
|
262
|
+
end
|
263
|
+
|
192
264
|
if @parser.finished?
|
193
265
|
return setup_body
|
194
266
|
elsif @parsed_bytes >= MAX_HEADER
|
@@ -201,13 +273,13 @@ module Puma
|
|
201
273
|
|
202
274
|
def eagerly_finish
|
203
275
|
return true if @ready
|
204
|
-
return false unless
|
276
|
+
return false unless @to_io.wait_readable(0)
|
205
277
|
try_to_finish
|
206
278
|
end
|
207
279
|
|
208
280
|
def finish(timeout)
|
209
281
|
return if @ready
|
210
|
-
|
282
|
+
@to_io.wait_readable(timeout) || timeout! until try_to_finish
|
211
283
|
end
|
212
284
|
|
213
285
|
def timeout!
|
@@ -226,7 +298,7 @@ module Puma
|
|
226
298
|
return @peerip if @peerip
|
227
299
|
|
228
300
|
if @remote_addr_header
|
229
|
-
hdr = (@env[@remote_addr_header] ||
|
301
|
+
hdr = (@env[@remote_addr_header] || @io.peeraddr.last).split(/[\s,]/).first
|
230
302
|
@peerip = hdr
|
231
303
|
return hdr
|
232
304
|
end
|
@@ -234,6 +306,16 @@ module Puma
|
|
234
306
|
@peerip ||= @io.peeraddr.last
|
235
307
|
end
|
236
308
|
|
309
|
+
def peer_family
|
310
|
+
return @peer_family if @peer_family
|
311
|
+
|
312
|
+
@peer_family ||= begin
|
313
|
+
@io.local_address.afamily
|
314
|
+
rescue
|
315
|
+
Socket::AF_INET
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
237
319
|
# Returns true if the persistent connection can be closed immediately
|
238
320
|
# without waiting for the configured idle/shutdown timeout.
|
239
321
|
# @version 5.0.0
|
@@ -243,10 +325,21 @@ module Puma
|
|
243
325
|
@parsed_bytes == 0
|
244
326
|
end
|
245
327
|
|
328
|
+
def expect_proxy_proto=(val)
|
329
|
+
if val
|
330
|
+
if @read_header
|
331
|
+
@read_proxy = true
|
332
|
+
end
|
333
|
+
else
|
334
|
+
@read_proxy = false
|
335
|
+
end
|
336
|
+
@expect_proxy_proto = val
|
337
|
+
end
|
338
|
+
|
246
339
|
private
|
247
340
|
|
248
341
|
def setup_body
|
249
|
-
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :
|
342
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
250
343
|
|
251
344
|
if @env[HTTP_EXPECT] == CONTINUE
|
252
345
|
# TODO allow a hook here to check the headers before
|
@@ -260,16 +353,27 @@ module Puma
|
|
260
353
|
body = @parser.body
|
261
354
|
|
262
355
|
te = @env[TRANSFER_ENCODING2]
|
263
|
-
|
264
356
|
if te
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
357
|
+
te_lwr = te.downcase
|
358
|
+
if te.include? ','
|
359
|
+
te_ary = te_lwr.split ','
|
360
|
+
te_count = te_ary.count CHUNKED
|
361
|
+
te_valid = te_ary[0..-2].all? { |e| ALLOWED_TRANSFER_ENCODING.include? e }
|
362
|
+
if te_ary.last == CHUNKED && te_count == 1 && te_valid
|
363
|
+
@env.delete TRANSFER_ENCODING2
|
364
|
+
return setup_chunked_body body
|
365
|
+
elsif te_count >= 1
|
366
|
+
raise HttpParserError , "#{TE_ERR_MSG}, multiple chunked: '#{te}'"
|
367
|
+
elsif !te_valid
|
368
|
+
raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
270
369
|
end
|
271
|
-
elsif
|
272
|
-
|
370
|
+
elsif te_lwr == CHUNKED
|
371
|
+
@env.delete TRANSFER_ENCODING2
|
372
|
+
return setup_chunked_body body
|
373
|
+
elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
|
374
|
+
raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
|
375
|
+
else
|
376
|
+
raise HttpParserError501 , "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
273
377
|
end
|
274
378
|
end
|
275
379
|
|
@@ -277,7 +381,12 @@ module Puma
|
|
277
381
|
|
278
382
|
cl = @env[CONTENT_LENGTH]
|
279
383
|
|
280
|
-
|
384
|
+
if cl
|
385
|
+
# cannot contain characters that are not \d
|
386
|
+
if CONTENT_LENGTH_VALUE_INVALID.match? cl
|
387
|
+
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
388
|
+
end
|
389
|
+
else
|
281
390
|
@buffer = body.empty? ? nil : body
|
282
391
|
@body = EmptyBody
|
283
392
|
set_ready
|
@@ -295,6 +404,7 @@ module Puma
|
|
295
404
|
|
296
405
|
if remain > MAX_BODY
|
297
406
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
407
|
+
@body.unlink
|
298
408
|
@body.binmode
|
299
409
|
@tempfile = @body
|
300
410
|
else
|
@@ -307,7 +417,7 @@ module Puma
|
|
307
417
|
|
308
418
|
@body_remain = remain
|
309
419
|
|
310
|
-
|
420
|
+
false
|
311
421
|
end
|
312
422
|
|
313
423
|
def read_body
|
@@ -326,7 +436,7 @@ module Puma
|
|
326
436
|
end
|
327
437
|
|
328
438
|
begin
|
329
|
-
chunk = @io.read_nonblock(want)
|
439
|
+
chunk = @io.read_nonblock(want, @read_buffer)
|
330
440
|
rescue IO::WaitReadable
|
331
441
|
return false
|
332
442
|
rescue SystemCallError, IOError
|
@@ -358,7 +468,7 @@ module Puma
|
|
358
468
|
def read_chunked_body
|
359
469
|
while true
|
360
470
|
begin
|
361
|
-
chunk = @io.read_nonblock(4096)
|
471
|
+
chunk = @io.read_nonblock(4096, @read_buffer)
|
362
472
|
rescue IO::WaitReadable
|
363
473
|
return false
|
364
474
|
rescue SystemCallError, IOError
|
@@ -386,6 +496,7 @@ module Puma
|
|
386
496
|
@prev_chunk = ""
|
387
497
|
|
388
498
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
499
|
+
@body.unlink
|
389
500
|
@body.binmode
|
390
501
|
@tempfile = @body
|
391
502
|
@chunked_content_length = 0
|
@@ -434,7 +545,13 @@ module Puma
|
|
434
545
|
while !io.eof?
|
435
546
|
line = io.gets
|
436
547
|
if line.end_with?("\r\n")
|
437
|
-
|
548
|
+
# Puma doesn't process chunk extensions, but should parse if they're
|
549
|
+
# present, which is the reason for the semicolon regex
|
550
|
+
chunk_hex = line.strip[/\A[^;]+/]
|
551
|
+
if CHUNK_SIZE_INVALID.match? chunk_hex
|
552
|
+
raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
|
553
|
+
end
|
554
|
+
len = chunk_hex.to_i(16)
|
438
555
|
if len == 0
|
439
556
|
@in_last_chunk = true
|
440
557
|
@body.rewind
|
@@ -465,7 +582,12 @@ module Puma
|
|
465
582
|
|
466
583
|
case
|
467
584
|
when got == len
|
468
|
-
|
585
|
+
# proper chunked segment must end with "\r\n"
|
586
|
+
if part.end_with? CHUNK_VALID_ENDING
|
587
|
+
write_chunk(part[0..-3]) # to skip the ending \r\n
|
588
|
+
else
|
589
|
+
raise HttpParserError, "Chunk size mismatch"
|
590
|
+
end
|
469
591
|
when got <= len - 2
|
470
592
|
write_chunk(part)
|
471
593
|
@partial_part_left = len - part.size
|
@@ -489,10 +611,14 @@ module Puma
|
|
489
611
|
|
490
612
|
def set_ready
|
491
613
|
if @body_read_start
|
492
|
-
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :
|
614
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - @body_read_start
|
493
615
|
end
|
494
616
|
@requests_served += 1
|
495
617
|
@ready = true
|
496
618
|
end
|
619
|
+
|
620
|
+
def above_http_content_limit(value)
|
621
|
+
@http_content_length_limit&.< value
|
622
|
+
end
|
497
623
|
end
|
498
624
|
end
|
data/lib/puma/cluster/worker.rb
CHANGED
@@ -2,27 +2,29 @@
|
|
2
2
|
|
3
3
|
module Puma
|
4
4
|
class Cluster < Puma::Runner
|
5
|
+
#—————————————————————— DO NOT USE — this class is for internal use only ———
|
6
|
+
|
7
|
+
|
5
8
|
# This class is instantiated by the `Puma::Cluster` and represents a single
|
6
9
|
# worker process.
|
7
10
|
#
|
8
11
|
# At the core of this class is running an instance of `Puma::Server` which
|
9
12
|
# gets created via the `start_server` method from the `Puma::Runner` class
|
10
13
|
# that this inherits from.
|
11
|
-
class Worker < Puma::Runner
|
14
|
+
class Worker < Puma::Runner # :nodoc:
|
12
15
|
attr_reader :index, :master
|
13
16
|
|
14
17
|
def initialize(index:, master:, launcher:, pipes:, server: nil)
|
15
|
-
super
|
18
|
+
super(launcher)
|
16
19
|
|
17
20
|
@index = index
|
18
21
|
@master = master
|
19
|
-
@launcher = launcher
|
20
|
-
@options = launcher.options
|
21
22
|
@check_pipe = pipes[:check_pipe]
|
22
23
|
@worker_write = pipes[:worker_write]
|
23
24
|
@fork_pipe = pipes[:fork_pipe]
|
24
25
|
@wakeup = pipes[:wakeup]
|
25
26
|
@server = server
|
27
|
+
@hook_data = {}
|
26
28
|
end
|
27
29
|
|
28
30
|
def run
|
@@ -34,8 +36,8 @@ module Puma
|
|
34
36
|
Signal.trap "SIGCHLD", "DEFAULT"
|
35
37
|
|
36
38
|
Thread.new do
|
37
|
-
Puma.set_thread_name "
|
38
|
-
|
39
|
+
Puma.set_thread_name "wrkr check"
|
40
|
+
@check_pipe.wait_readable
|
39
41
|
log "! Detected parent died, dying"
|
40
42
|
exit! 1
|
41
43
|
end
|
@@ -52,9 +54,17 @@ module Puma
|
|
52
54
|
|
53
55
|
# Invoke any worker boot hooks so they can get
|
54
56
|
# things in shape before booting the app.
|
55
|
-
@
|
57
|
+
@config.run_hooks(:before_worker_boot, index, @log_writer, @hook_data)
|
56
58
|
|
59
|
+
begin
|
57
60
|
server = @server ||= start_server
|
61
|
+
rescue Exception => e
|
62
|
+
log "! Unable to start worker"
|
63
|
+
log e
|
64
|
+
log e.backtrace.join("\n ")
|
65
|
+
exit 1
|
66
|
+
end
|
67
|
+
|
58
68
|
restart_server = Queue.new << true << false
|
59
69
|
|
60
70
|
fork_worker = @options[:fork_worker] && index == 0
|
@@ -69,15 +79,14 @@ module Puma
|
|
69
79
|
end
|
70
80
|
|
71
81
|
Thread.new do
|
72
|
-
Puma.set_thread_name "
|
82
|
+
Puma.set_thread_name "wrkr fork"
|
73
83
|
while (idx = @fork_pipe.gets)
|
74
84
|
idx = idx.to_i
|
75
85
|
if idx == -1 # stop server
|
76
86
|
if restart_server.length > 0
|
77
87
|
restart_server.clear
|
78
88
|
server.begin_restart(true)
|
79
|
-
@
|
80
|
-
Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
|
89
|
+
@config.run_hooks(:before_refork, nil, @log_writer, @hook_data)
|
81
90
|
end
|
82
91
|
elsif idx == 0 # restart server
|
83
92
|
restart_server << true << false
|
@@ -99,15 +108,20 @@ module Puma
|
|
99
108
|
begin
|
100
109
|
@worker_write << "b#{Process.pid}:#{index}\n"
|
101
110
|
rescue SystemCallError, IOError
|
102
|
-
|
111
|
+
Puma::Util.purge_interrupt_queue
|
103
112
|
STDERR.puts "Master seems to have exited, exiting."
|
104
113
|
return
|
105
114
|
end
|
106
115
|
|
107
116
|
while restart_server.pop
|
108
117
|
server_thread = server.run
|
118
|
+
|
119
|
+
if @log_writer.debug? && index == 0
|
120
|
+
debug_loaded_extensions "Loaded Extensions - worker 0:"
|
121
|
+
end
|
122
|
+
|
109
123
|
stat_thread ||= Thread.new(@worker_write) do |io|
|
110
|
-
Puma.set_thread_name "stat
|
124
|
+
Puma.set_thread_name "stat pld"
|
111
125
|
base_payload = "p#{Process.pid}"
|
112
126
|
|
113
127
|
while true
|
@@ -120,10 +134,10 @@ module Puma
|
|
120
134
|
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
|
121
135
|
io << payload
|
122
136
|
rescue IOError
|
123
|
-
|
137
|
+
Puma::Util.purge_interrupt_queue
|
124
138
|
break
|
125
139
|
end
|
126
|
-
sleep
|
140
|
+
sleep @options[:worker_check_interval]
|
127
141
|
end
|
128
142
|
end
|
129
143
|
server_thread.join
|
@@ -131,7 +145,7 @@ module Puma
|
|
131
145
|
|
132
146
|
# Invoke any worker shutdown hooks so they can prevent the worker
|
133
147
|
# exiting until any background operations are completed
|
134
|
-
@
|
148
|
+
@config.run_hooks(:before_worker_shutdown, index, @log_writer, @hook_data)
|
135
149
|
ensure
|
136
150
|
@worker_write << "t#{Process.pid}\n" rescue nil
|
137
151
|
@worker_write.close
|
@@ -140,7 +154,7 @@ module Puma
|
|
140
154
|
private
|
141
155
|
|
142
156
|
def spawn_worker(idx)
|
143
|
-
@
|
157
|
+
@config.run_hooks(:before_worker_fork, idx, @log_writer, @hook_data)
|
144
158
|
|
145
159
|
pid = fork do
|
146
160
|
new_worker = Worker.new index: idx,
|
@@ -158,19 +172,9 @@ module Puma
|
|
158
172
|
exit! 1
|
159
173
|
end
|
160
174
|
|
161
|
-
@
|
175
|
+
@config.run_hooks(:after_worker_fork, idx, @log_writer, @hook_data)
|
162
176
|
pid
|
163
177
|
end
|
164
|
-
|
165
|
-
def wakeup!
|
166
|
-
return unless @wakeup
|
167
|
-
|
168
|
-
begin
|
169
|
-
@wakeup.write "!" unless @wakeup.closed?
|
170
|
-
rescue SystemCallError, IOError
|
171
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
172
|
-
end
|
173
|
-
end
|
174
178
|
end
|
175
179
|
end
|
176
180
|
end
|
@@ -2,12 +2,15 @@
|
|
2
2
|
|
3
3
|
module Puma
|
4
4
|
class Cluster < Runner
|
5
|
+
#—————————————————————— DO NOT USE — this class is for internal use only ———
|
6
|
+
|
7
|
+
|
5
8
|
# This class represents a worker process from the perspective of the puma
|
6
9
|
# master process. It contains information about the process and its health
|
7
10
|
# and it exposes methods to control the process via IPC. It does not
|
8
11
|
# include the actual logic executed by the worker process itself. For that,
|
9
12
|
# see Puma::Cluster::Worker.
|
10
|
-
class WorkerHandle
|
13
|
+
class WorkerHandle # :nodoc:
|
11
14
|
def initialize(idx, pid, phase, options)
|
12
15
|
@index = idx
|
13
16
|
@pid = pid
|
@@ -31,11 +34,19 @@ module Puma
|
|
31
34
|
@stage == :booted
|
32
35
|
end
|
33
36
|
|
37
|
+
def uptime
|
38
|
+
Time.now - started_at
|
39
|
+
end
|
40
|
+
|
34
41
|
def boot!
|
35
42
|
@last_checkin = Time.now
|
36
43
|
@stage = :booted
|
37
44
|
end
|
38
45
|
|
46
|
+
def term!
|
47
|
+
@term = true
|
48
|
+
end
|
49
|
+
|
39
50
|
def term?
|
40
51
|
@term
|
41
52
|
end
|