puma 6.4.3 → 7.2.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 +4 -4
- data/History.md +387 -8
- data/README.md +109 -49
- data/docs/deployment.md +58 -23
- data/docs/fork_worker.md +11 -1
- data/docs/java_options.md +54 -0
- data/docs/jungle/README.md +1 -1
- data/docs/kubernetes.md +11 -16
- data/docs/plugins.md +6 -2
- data/docs/restart.md +2 -2
- data/docs/signals.md +21 -21
- data/docs/stats.md +11 -5
- data/docs/systemd.md +14 -5
- data/ext/puma_http11/extconf.rb +20 -32
- data/ext/puma_http11/mini_ssl.c +29 -9
- data/ext/puma_http11/org/jruby/puma/Http11.java +38 -9
- data/ext/puma_http11/puma_http11.c +125 -118
- data/lib/puma/app/status.rb +11 -3
- data/lib/puma/binder.rb +21 -11
- data/lib/puma/cli.rb +10 -8
- data/lib/puma/client.rb +157 -84
- data/lib/puma/cluster/worker.rb +24 -21
- data/lib/puma/cluster/worker_handle.rb +38 -8
- data/lib/puma/cluster.rb +73 -47
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +3 -3
- data/lib/puma/configuration.rb +131 -60
- data/lib/puma/const.rb +23 -12
- data/lib/puma/control_cli.rb +10 -6
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +411 -121
- data/lib/puma/error_logger.rb +7 -5
- data/lib/puma/events.rb +25 -10
- data/lib/puma/io_buffer.rb +8 -4
- data/lib/puma/jruby_restart.rb +0 -16
- data/lib/puma/launcher/bundle_pruner.rb +1 -1
- data/lib/puma/launcher.rb +73 -55
- data/lib/puma/log_writer.rb +9 -9
- data/lib/puma/minissl/context_builder.rb +1 -0
- data/lib/puma/minissl.rb +1 -1
- data/lib/puma/null_io.rb +26 -0
- data/lib/puma/plugin/systemd.rb +3 -3
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/reactor.rb +19 -13
- data/lib/puma/request.rb +55 -36
- data/lib/puma/runner.rb +15 -17
- data/lib/puma/sd_notify.rb +1 -4
- data/lib/puma/server.rb +134 -73
- data/lib/puma/single.rb +7 -4
- data/lib/puma/state_file.rb +3 -2
- data/lib/puma/thread_pool.rb +57 -80
- data/lib/puma/util.rb +0 -7
- data/lib/puma.rb +10 -0
- data/lib/rack/handler/puma.rb +10 -7
- data/tools/Dockerfile +15 -5
- metadata +14 -15
- data/ext/puma_http11/ext_help.h +0 -15
data/lib/puma/client.rb
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class IO
|
|
4
|
-
# We need to use this for a jruby work around on both 1.8 and 1.9.
|
|
5
|
-
# So this either creates the constant (on 1.8), or harmlessly
|
|
6
|
-
# reopens it (on 1.9).
|
|
7
|
-
module WaitReadable
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
|
-
|
|
11
3
|
require_relative 'detect'
|
|
12
4
|
require_relative 'io_buffer'
|
|
13
5
|
require 'tempfile'
|
|
@@ -64,6 +56,11 @@ module Puma
|
|
|
64
56
|
|
|
65
57
|
TE_ERR_MSG = 'Invalid Transfer-Encoding'
|
|
66
58
|
|
|
59
|
+
# See:
|
|
60
|
+
# https://httpwg.org/specs/rfc9110.html#rfc.section.5.6.1.1
|
|
61
|
+
# https://httpwg.org/specs/rfc9112.html#rfc.section.6.1
|
|
62
|
+
STRIP_OWS = /\A[ \t]+|[ \t]+\z/
|
|
63
|
+
|
|
67
64
|
# The object used for a request with no body. All requests with
|
|
68
65
|
# no body share this one object since it has no state.
|
|
69
66
|
EmptyBody = NullIO.new
|
|
@@ -111,7 +108,8 @@ module Puma
|
|
|
111
108
|
end
|
|
112
109
|
|
|
113
110
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
|
114
|
-
:tempfile, :io_buffer, :http_content_length_limit_exceeded
|
|
111
|
+
:tempfile, :io_buffer, :http_content_length_limit_exceeded,
|
|
112
|
+
:requests_served
|
|
115
113
|
|
|
116
114
|
attr_writer :peerip, :http_content_length_limit
|
|
117
115
|
|
|
@@ -133,9 +131,9 @@ module Puma
|
|
|
133
131
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
|
134
132
|
end
|
|
135
133
|
|
|
136
|
-
# For the hijack protocol
|
|
137
|
-
#
|
|
138
|
-
def
|
|
134
|
+
# For the full hijack protocol, `env['rack.hijack']` is set to
|
|
135
|
+
# `client.method :full_hijack`
|
|
136
|
+
def full_hijack
|
|
139
137
|
@hijacked = true
|
|
140
138
|
env[HIJACK_IO] ||= @io
|
|
141
139
|
end
|
|
@@ -150,29 +148,31 @@ module Puma
|
|
|
150
148
|
end
|
|
151
149
|
|
|
152
150
|
# Number of seconds until the timeout elapses.
|
|
151
|
+
# @!attribute [r] timeout
|
|
153
152
|
def timeout
|
|
154
153
|
[@timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
|
|
155
154
|
end
|
|
156
155
|
|
|
157
|
-
def reset
|
|
156
|
+
def reset
|
|
158
157
|
@parser.reset
|
|
159
158
|
@io_buffer.reset
|
|
160
159
|
@read_header = true
|
|
161
|
-
@read_proxy = !!@expect_proxy_proto
|
|
160
|
+
@read_proxy = !!@expect_proxy_proto && @requests_served.zero?
|
|
162
161
|
@env = @proto_env.dup
|
|
163
|
-
@body = nil
|
|
164
|
-
@tempfile = nil
|
|
165
162
|
@parsed_bytes = 0
|
|
166
163
|
@ready = false
|
|
167
164
|
@body_remain = 0
|
|
168
165
|
@peerip = nil if @remote_addr_header
|
|
169
166
|
@in_last_chunk = false
|
|
170
167
|
@http_content_length_limit_exceeded = false
|
|
168
|
+
end
|
|
171
169
|
|
|
170
|
+
# only used with back-to-back requests contained in the buffer
|
|
171
|
+
def process_back_to_back_requests
|
|
172
172
|
if @buffer
|
|
173
173
|
return false unless try_to_parse_proxy_protocol
|
|
174
174
|
|
|
175
|
-
@parsed_bytes =
|
|
175
|
+
@parsed_bytes = parser_execute
|
|
176
176
|
|
|
177
177
|
if @parser.finished?
|
|
178
178
|
return setup_body
|
|
@@ -180,47 +180,67 @@ module Puma
|
|
|
180
180
|
raise HttpParserError,
|
|
181
181
|
"HEADER is longer than allowed, aborting client early."
|
|
182
182
|
end
|
|
183
|
-
|
|
184
|
-
return false
|
|
185
|
-
else
|
|
186
|
-
begin
|
|
187
|
-
if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
|
|
188
|
-
return try_to_finish
|
|
189
|
-
end
|
|
190
|
-
rescue IOError
|
|
191
|
-
# swallow it
|
|
192
|
-
end
|
|
193
|
-
|
|
194
183
|
end
|
|
195
184
|
end
|
|
196
185
|
|
|
186
|
+
# if a client sends back-to-back requests, the buffer may contain one or more
|
|
187
|
+
# of them.
|
|
188
|
+
def has_back_to_back_requests?
|
|
189
|
+
!(@buffer.nil? || @buffer.empty?)
|
|
190
|
+
end
|
|
191
|
+
|
|
197
192
|
def close
|
|
193
|
+
tempfile_close
|
|
198
194
|
begin
|
|
199
195
|
@io.close
|
|
200
196
|
rescue IOError, Errno::EBADF
|
|
201
|
-
Puma::Util.purge_interrupt_queue
|
|
202
197
|
end
|
|
203
198
|
end
|
|
204
199
|
|
|
200
|
+
def tempfile_close
|
|
201
|
+
tf_path = @tempfile&.path
|
|
202
|
+
@tempfile&.close
|
|
203
|
+
File.unlink(tf_path) if tf_path
|
|
204
|
+
@tempfile = nil
|
|
205
|
+
@body = nil
|
|
206
|
+
rescue Errno::ENOENT, IOError
|
|
207
|
+
end
|
|
208
|
+
|
|
205
209
|
# If necessary, read the PROXY protocol from the buffer. Returns
|
|
206
210
|
# false if more data is needed.
|
|
207
211
|
def try_to_parse_proxy_protocol
|
|
208
212
|
if @read_proxy
|
|
209
213
|
if @expect_proxy_proto == :v1
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
crlf_index = @buffer.index "\r\n"
|
|
215
|
+
|
|
216
|
+
unless crlf_index
|
|
217
|
+
if "PROXY ".start_with? @buffer
|
|
218
|
+
return false
|
|
219
|
+
elsif @buffer.start_with? "PROXY "
|
|
220
|
+
if @buffer.bytesize >= PROXY_PROTOCOL_V1_MAX_LENGTH
|
|
221
|
+
raise ConnectionError, "PROXY protocol v1 line is too long"
|
|
214
222
|
end
|
|
215
|
-
|
|
223
|
+
return false
|
|
216
224
|
end
|
|
217
|
-
|
|
218
|
-
# request, this is just HTTP from a non-PROXY client; move on
|
|
225
|
+
|
|
219
226
|
@read_proxy = false
|
|
220
|
-
return
|
|
221
|
-
|
|
222
|
-
|
|
227
|
+
return true
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
if @buffer.start_with?("PROXY ") && crlf_index + 2 > PROXY_PROTOCOL_V1_MAX_LENGTH
|
|
231
|
+
raise ConnectionError, "PROXY protocol v1 line is too long"
|
|
223
232
|
end
|
|
233
|
+
|
|
234
|
+
if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
|
|
235
|
+
if md[1]
|
|
236
|
+
@peerip = md[1].split(" ")[0]
|
|
237
|
+
end
|
|
238
|
+
@buffer = md.post_match
|
|
239
|
+
end
|
|
240
|
+
# if the buffer has a \r\n but doesn't have a PROXY protocol
|
|
241
|
+
# request, this is just HTTP from a non-PROXY client; move on
|
|
242
|
+
@read_proxy = false
|
|
243
|
+
return @buffer.size > 0
|
|
224
244
|
end
|
|
225
245
|
end
|
|
226
246
|
true
|
|
@@ -240,6 +260,7 @@ module Puma
|
|
|
240
260
|
|
|
241
261
|
return read_body if in_data_phase
|
|
242
262
|
|
|
263
|
+
data = nil
|
|
243
264
|
begin
|
|
244
265
|
data = @io.read_nonblock(CHUNK_SIZE)
|
|
245
266
|
rescue IO::WaitReadable
|
|
@@ -265,26 +286,28 @@ module Puma
|
|
|
265
286
|
|
|
266
287
|
return false unless try_to_parse_proxy_protocol
|
|
267
288
|
|
|
268
|
-
@parsed_bytes =
|
|
289
|
+
@parsed_bytes = parser_execute
|
|
269
290
|
|
|
270
291
|
if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
|
|
271
292
|
@http_content_length_limit_exceeded = true
|
|
272
293
|
end
|
|
273
294
|
|
|
274
295
|
if @parser.finished?
|
|
275
|
-
|
|
296
|
+
setup_body
|
|
276
297
|
elsif @parsed_bytes >= MAX_HEADER
|
|
277
298
|
raise HttpParserError,
|
|
278
299
|
"HEADER is longer than allowed, aborting client early."
|
|
300
|
+
else
|
|
301
|
+
false
|
|
279
302
|
end
|
|
280
|
-
|
|
281
|
-
false
|
|
282
303
|
end
|
|
283
304
|
|
|
284
305
|
def eagerly_finish
|
|
285
306
|
return true if @ready
|
|
286
|
-
|
|
287
|
-
|
|
307
|
+
while @to_io.wait_readable(0) # rubocop: disable Style/WhileUntilModifier
|
|
308
|
+
return true if try_to_finish
|
|
309
|
+
end
|
|
310
|
+
false
|
|
288
311
|
end
|
|
289
312
|
|
|
290
313
|
def finish(timeout)
|
|
@@ -292,6 +315,44 @@ module Puma
|
|
|
292
315
|
@to_io.wait_readable(timeout) || timeout! until try_to_finish
|
|
293
316
|
end
|
|
294
317
|
|
|
318
|
+
# Wraps `@parser.execute` and adds meaningful error messages
|
|
319
|
+
# @return [Integer] bytes of buffer read by parser
|
|
320
|
+
#
|
|
321
|
+
def parser_execute
|
|
322
|
+
@parser.execute(@env, @buffer, @parsed_bytes)
|
|
323
|
+
rescue => e
|
|
324
|
+
@env[HTTP_CONNECTION] = 'close'
|
|
325
|
+
raise e unless HttpParserError === e && e.message.include?('non-SSL')
|
|
326
|
+
|
|
327
|
+
req, _ = @buffer.split "\r\n\r\n"
|
|
328
|
+
request_line, headers = req.split "\r\n", 2
|
|
329
|
+
|
|
330
|
+
# below checks for request issues and changes error message accordingly
|
|
331
|
+
if !@env.key? REQUEST_METHOD
|
|
332
|
+
if request_line.count(' ') != 2
|
|
333
|
+
# maybe this is an SSL connection ?
|
|
334
|
+
raise e
|
|
335
|
+
else
|
|
336
|
+
method = request_line[/\A[^ ]+/]
|
|
337
|
+
raise e, "Invalid HTTP format, parsing fails. Bad method #{method}"
|
|
338
|
+
end
|
|
339
|
+
elsif !@env.key? REQUEST_PATH
|
|
340
|
+
path = request_line[/\A[^ ]+ +([^ ?\r\n]+)/, 1]
|
|
341
|
+
raise e, "Invalid HTTP format, parsing fails. Bad path #{path}"
|
|
342
|
+
elsif request_line.match?(/\A[^ ]+ +[^ ?\r\n]+\?/) && !@env.key?(QUERY_STRING)
|
|
343
|
+
query = request_line[/\A[^ ]+ +[^? ]+\?([^ ]+)/, 1]
|
|
344
|
+
raise e, "Invalid HTTP format, parsing fails. Bad query #{query}"
|
|
345
|
+
elsif !@env.key? SERVER_PROTOCOL
|
|
346
|
+
# protocol is bad
|
|
347
|
+
text = request_line[/[^ ]*\z/]
|
|
348
|
+
raise HttpParserError, "Invalid HTTP format, parsing fails. Bad protocol #{text}"
|
|
349
|
+
elsif !headers.empty?
|
|
350
|
+
# headers are bad
|
|
351
|
+
hdrs = headers.split("\r\n").map { |h| h.gsub "\n", '\n'}.join "\n"
|
|
352
|
+
raise HttpParserError, "Invalid HTTP format, parsing fails. Bad headers\n#{hdrs}"
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
295
356
|
def timeout!
|
|
296
357
|
write_error(408) if in_data_phase
|
|
297
358
|
raise ConnectionError
|
|
@@ -366,17 +427,18 @@ module Puma
|
|
|
366
427
|
if te
|
|
367
428
|
te_lwr = te.downcase
|
|
368
429
|
if te.include? ','
|
|
369
|
-
te_ary = te_lwr.split
|
|
430
|
+
te_ary = te_lwr.split(',').each { |te| te.gsub!(STRIP_OWS, "") }
|
|
370
431
|
te_count = te_ary.count CHUNKED
|
|
371
432
|
te_valid = te_ary[0..-2].all? { |e| ALLOWED_TRANSFER_ENCODING.include? e }
|
|
372
|
-
if
|
|
373
|
-
@env.delete TRANSFER_ENCODING2
|
|
374
|
-
return setup_chunked_body body
|
|
375
|
-
elsif te_count >= 1
|
|
433
|
+
if te_count > 1
|
|
376
434
|
raise HttpParserError , "#{TE_ERR_MSG}, multiple chunked: '#{te}'"
|
|
435
|
+
elsif te_ary.last != CHUNKED
|
|
436
|
+
raise HttpParserError , "#{TE_ERR_MSG}, last value must be chunked: '#{te}'"
|
|
377
437
|
elsif !te_valid
|
|
378
438
|
raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
|
379
439
|
end
|
|
440
|
+
@env.delete TRANSFER_ENCODING2
|
|
441
|
+
return setup_chunked_body body
|
|
380
442
|
elsif te_lwr == CHUNKED
|
|
381
443
|
@env.delete TRANSFER_ENCODING2
|
|
382
444
|
return setup_chunked_body body
|
|
@@ -403,18 +465,33 @@ module Puma
|
|
|
403
465
|
return true
|
|
404
466
|
end
|
|
405
467
|
|
|
406
|
-
|
|
468
|
+
content_length = cl.to_i
|
|
469
|
+
|
|
470
|
+
remain = content_length - body.bytesize
|
|
407
471
|
|
|
408
472
|
if remain <= 0
|
|
409
|
-
|
|
410
|
-
|
|
473
|
+
# Part of the body is a pipelined request OR garbage. We'll deal with that later.
|
|
474
|
+
if content_length == 0
|
|
475
|
+
@body = EmptyBody
|
|
476
|
+
if body.empty?
|
|
477
|
+
@buffer = nil
|
|
478
|
+
else
|
|
479
|
+
@buffer = body
|
|
480
|
+
end
|
|
481
|
+
elsif remain == 0
|
|
482
|
+
@body = StringIO.new body
|
|
483
|
+
@buffer = nil
|
|
484
|
+
else
|
|
485
|
+
@body = StringIO.new(body[0,content_length])
|
|
486
|
+
@buffer = body[content_length..-1]
|
|
487
|
+
end
|
|
411
488
|
set_ready
|
|
412
489
|
return true
|
|
413
490
|
end
|
|
414
491
|
|
|
415
492
|
if remain > MAX_BODY
|
|
416
|
-
@body = Tempfile.
|
|
417
|
-
@body.
|
|
493
|
+
@body = Tempfile.create(Const::PUMA_TMP_BASE)
|
|
494
|
+
File.unlink @body.path unless IS_WINDOWS
|
|
418
495
|
@body.binmode
|
|
419
496
|
@tempfile = @body
|
|
420
497
|
else
|
|
@@ -439,46 +516,42 @@ module Puma
|
|
|
439
516
|
# after this
|
|
440
517
|
remain = @body_remain
|
|
441
518
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
519
|
+
# don't bother with reading zero bytes
|
|
520
|
+
unless remain.zero?
|
|
521
|
+
begin
|
|
522
|
+
chunk = @io.read_nonblock(remain.clamp(0, CHUNK_SIZE), @read_buffer)
|
|
523
|
+
rescue IO::WaitReadable
|
|
524
|
+
return false
|
|
525
|
+
rescue SystemCallError, IOError
|
|
526
|
+
raise ConnectionError, "Connection error detected during read"
|
|
527
|
+
end
|
|
447
528
|
|
|
448
|
-
|
|
449
|
-
chunk
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
529
|
+
# No chunk means a closed socket
|
|
530
|
+
unless chunk
|
|
531
|
+
@body.close
|
|
532
|
+
@buffer = nil
|
|
533
|
+
set_ready
|
|
534
|
+
raise EOFError
|
|
535
|
+
end
|
|
455
536
|
|
|
456
|
-
|
|
457
|
-
unless chunk
|
|
458
|
-
@body.close
|
|
459
|
-
@buffer = nil
|
|
460
|
-
set_ready
|
|
461
|
-
raise EOFError
|
|
537
|
+
remain -= @body.write(chunk)
|
|
462
538
|
end
|
|
463
539
|
|
|
464
|
-
remain -= @body.write(chunk)
|
|
465
|
-
|
|
466
540
|
if remain <= 0
|
|
467
541
|
@body.rewind
|
|
468
542
|
@buffer = nil
|
|
469
543
|
set_ready
|
|
470
|
-
|
|
544
|
+
true
|
|
545
|
+
else
|
|
546
|
+
@body_remain = remain
|
|
547
|
+
false
|
|
471
548
|
end
|
|
472
|
-
|
|
473
|
-
@body_remain = remain
|
|
474
|
-
|
|
475
|
-
false
|
|
476
549
|
end
|
|
477
550
|
|
|
478
551
|
def read_chunked_body
|
|
479
552
|
while true
|
|
480
553
|
begin
|
|
481
|
-
chunk = @io.read_nonblock(
|
|
554
|
+
chunk = @io.read_nonblock(CHUNK_SIZE, @read_buffer)
|
|
482
555
|
rescue IO::WaitReadable
|
|
483
556
|
return false
|
|
484
557
|
rescue SystemCallError, IOError
|
|
@@ -506,8 +579,8 @@ module Puma
|
|
|
506
579
|
@prev_chunk = ""
|
|
507
580
|
@excess_cr = 0
|
|
508
581
|
|
|
509
|
-
@body = Tempfile.
|
|
510
|
-
@body.
|
|
582
|
+
@body = Tempfile.create(Const::PUMA_TMP_BASE)
|
|
583
|
+
File.unlink @body.path unless IS_WINDOWS
|
|
511
584
|
@body.binmode
|
|
512
585
|
@tempfile = @body
|
|
513
586
|
@chunked_content_length = 0
|
|
@@ -627,7 +700,7 @@ module Puma
|
|
|
627
700
|
@partial_part_left = len - part.size
|
|
628
701
|
end
|
|
629
702
|
else
|
|
630
|
-
if @prev_chunk.size +
|
|
703
|
+
if @prev_chunk.size + line.size >= MAX_CHUNK_HEADER_SIZE
|
|
631
704
|
raise HttpParserError, "maximum size of chunk header exceeded"
|
|
632
705
|
end
|
|
633
706
|
|
data/lib/puma/cluster/worker.rb
CHANGED
|
@@ -14,7 +14,7 @@ module Puma
|
|
|
14
14
|
class Worker < Puma::Runner # :nodoc:
|
|
15
15
|
attr_reader :index, :master
|
|
16
16
|
|
|
17
|
-
def initialize(index:, master:, launcher:, pipes:,
|
|
17
|
+
def initialize(index:, master:, launcher:, pipes:, app: nil)
|
|
18
18
|
super(launcher)
|
|
19
19
|
|
|
20
20
|
@index = index
|
|
@@ -23,7 +23,8 @@ module Puma
|
|
|
23
23
|
@worker_write = pipes[:worker_write]
|
|
24
24
|
@fork_pipe = pipes[:fork_pipe]
|
|
25
25
|
@wakeup = pipes[:wakeup]
|
|
26
|
-
@
|
|
26
|
+
@app = app
|
|
27
|
+
@server = nil
|
|
27
28
|
@hook_data = {}
|
|
28
29
|
end
|
|
29
30
|
|
|
@@ -57,7 +58,7 @@ module Puma
|
|
|
57
58
|
@config.run_hooks(:before_worker_boot, index, @log_writer, @hook_data)
|
|
58
59
|
|
|
59
60
|
begin
|
|
60
|
-
|
|
61
|
+
@server = start_server
|
|
61
62
|
rescue Exception => e
|
|
62
63
|
log "! Unable to start worker"
|
|
63
64
|
log e
|
|
@@ -85,36 +86,37 @@ module Puma
|
|
|
85
86
|
if idx == -1 # stop server
|
|
86
87
|
if restart_server.length > 0
|
|
87
88
|
restart_server.clear
|
|
88
|
-
server.begin_restart(true)
|
|
89
|
+
@server.begin_restart(true)
|
|
89
90
|
@config.run_hooks(:before_refork, nil, @log_writer, @hook_data)
|
|
90
91
|
end
|
|
92
|
+
elsif idx == -2 # refork cycle is done
|
|
93
|
+
@config.run_hooks(:after_refork, nil, @log_writer, @hook_data)
|
|
91
94
|
elsif idx == 0 # restart server
|
|
92
95
|
restart_server << true << false
|
|
93
96
|
else # fork worker
|
|
94
97
|
worker_pids << pid = spawn_worker(idx)
|
|
95
|
-
@worker_write << "
|
|
98
|
+
@worker_write << "#{PIPE_FORK}#{pid}:#{idx}\n" rescue nil
|
|
96
99
|
end
|
|
97
100
|
end
|
|
98
101
|
end
|
|
99
102
|
end
|
|
100
103
|
|
|
101
104
|
Signal.trap "SIGTERM" do
|
|
102
|
-
@worker_write << "
|
|
105
|
+
@worker_write << "#{PIPE_EXTERNAL_TERM}#{Process.pid}\n" rescue nil
|
|
103
106
|
restart_server.clear
|
|
104
|
-
server.stop
|
|
107
|
+
@server.stop
|
|
105
108
|
restart_server << false
|
|
106
109
|
end
|
|
107
110
|
|
|
108
111
|
begin
|
|
109
|
-
@worker_write << "
|
|
112
|
+
@worker_write << "#{PIPE_BOOT}#{Process.pid}:#{index}\n"
|
|
110
113
|
rescue SystemCallError, IOError
|
|
111
|
-
Puma::Util.purge_interrupt_queue
|
|
112
114
|
STDERR.puts "Master seems to have exited, exiting."
|
|
113
115
|
return
|
|
114
116
|
end
|
|
115
117
|
|
|
116
118
|
while restart_server.pop
|
|
117
|
-
server_thread = server.run
|
|
119
|
+
server_thread = @server.run
|
|
118
120
|
|
|
119
121
|
if @log_writer.debug? && index == 0
|
|
120
122
|
debug_loaded_extensions "Loaded Extensions - worker 0:"
|
|
@@ -122,19 +124,20 @@ module Puma
|
|
|
122
124
|
|
|
123
125
|
stat_thread ||= Thread.new(@worker_write) do |io|
|
|
124
126
|
Puma.set_thread_name "stat pld"
|
|
125
|
-
base_payload = "
|
|
127
|
+
base_payload = "#{PIPE_PING}#{Process.pid}"
|
|
126
128
|
|
|
127
129
|
while true
|
|
128
130
|
begin
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
payload = base_payload.dup
|
|
132
|
+
|
|
133
|
+
hsh = @server.stats
|
|
134
|
+
hsh.each do |k, v|
|
|
135
|
+
payload << %Q! "#{k}":#{v || 0},!
|
|
136
|
+
end
|
|
137
|
+
# sub call properly adds 'closing' string
|
|
138
|
+
io << payload.sub(/,\z/, " }\n")
|
|
139
|
+
@server.reset_max
|
|
136
140
|
rescue IOError
|
|
137
|
-
Puma::Util.purge_interrupt_queue
|
|
138
141
|
break
|
|
139
142
|
end
|
|
140
143
|
sleep @options[:worker_check_interval]
|
|
@@ -147,7 +150,7 @@ module Puma
|
|
|
147
150
|
# exiting until any background operations are completed
|
|
148
151
|
@config.run_hooks(:before_worker_shutdown, index, @log_writer, @hook_data)
|
|
149
152
|
ensure
|
|
150
|
-
@worker_write << "
|
|
153
|
+
@worker_write << "#{PIPE_TERM}#{Process.pid}\n" rescue nil
|
|
151
154
|
@worker_write.close
|
|
152
155
|
end
|
|
153
156
|
|
|
@@ -162,7 +165,7 @@ module Puma
|
|
|
162
165
|
launcher: @launcher,
|
|
163
166
|
pipes: { check_pipe: @check_pipe,
|
|
164
167
|
worker_write: @worker_write },
|
|
165
|
-
|
|
168
|
+
app: @app
|
|
166
169
|
new_worker.run
|
|
167
170
|
end
|
|
168
171
|
|
|
@@ -4,13 +4,15 @@ module Puma
|
|
|
4
4
|
class Cluster < Runner
|
|
5
5
|
#—————————————————————— DO NOT USE — this class is for internal use only ———
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
# This class represents a worker process from the perspective of the puma
|
|
9
8
|
# master process. It contains information about the process and its health
|
|
10
9
|
# and it exposes methods to control the process via IPC. It does not
|
|
11
10
|
# include the actual logic executed by the worker process itself. For that,
|
|
12
11
|
# see Puma::Cluster::Worker.
|
|
13
12
|
class WorkerHandle # :nodoc:
|
|
13
|
+
# array of stat 'max' keys
|
|
14
|
+
WORKER_MAX_KEYS = [:backlog_max, :reactor_max]
|
|
15
|
+
|
|
14
16
|
def initialize(idx, pid, phase, options)
|
|
15
17
|
@index = idx
|
|
16
18
|
@pid = pid
|
|
@@ -23,12 +25,13 @@ module Puma
|
|
|
23
25
|
@last_checkin = Time.now
|
|
24
26
|
@last_status = {}
|
|
25
27
|
@term = false
|
|
28
|
+
@worker_max = Array.new WORKER_MAX_KEYS.length, 0
|
|
26
29
|
end
|
|
27
30
|
|
|
28
|
-
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
|
31
|
+
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at, :process_status
|
|
29
32
|
|
|
30
33
|
# @version 5.0.0
|
|
31
|
-
attr_writer :pid, :phase
|
|
34
|
+
attr_writer :pid, :phase, :process_status
|
|
32
35
|
|
|
33
36
|
def booted?
|
|
34
37
|
@stage == :booted
|
|
@@ -52,12 +55,39 @@ module Puma
|
|
|
52
55
|
end
|
|
53
56
|
|
|
54
57
|
def ping!(status)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
hsh = {}
|
|
59
|
+
k, v = nil, nil
|
|
60
|
+
status.tr('}{"', '').strip.split(", ") do |kv|
|
|
61
|
+
cntr = 0
|
|
62
|
+
kv.split(':') do |t|
|
|
63
|
+
if cntr == 0
|
|
64
|
+
k = t
|
|
65
|
+
cntr = 1
|
|
66
|
+
else
|
|
67
|
+
v = t
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
hsh[k.to_sym] = v.to_i
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# check stat max values, we can't signal workers to reset the max values,
|
|
74
|
+
# so we do so here
|
|
75
|
+
WORKER_MAX_KEYS.each_with_index do |key, idx|
|
|
76
|
+
next unless hsh[key]
|
|
77
|
+
|
|
78
|
+
if hsh[key] < @worker_max[idx]
|
|
79
|
+
hsh[key] = @worker_max[idx]
|
|
80
|
+
else
|
|
81
|
+
@worker_max[idx] = hsh[key]
|
|
82
|
+
end
|
|
60
83
|
end
|
|
84
|
+
@last_checkin = Time.now
|
|
85
|
+
@last_status = hsh
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Resets max values to zero. Called whenever `Cluster#stats` is called
|
|
89
|
+
def reset_max
|
|
90
|
+
WORKER_MAX_KEYS.length.times { |idx| @worker_max[idx] = 0 }
|
|
61
91
|
end
|
|
62
92
|
|
|
63
93
|
# @see Puma::Cluster#check_workers
|