puma 4.3.12 → 6.0.2
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 +1618 -521
- data/LICENSE +23 -20
- data/README.md +130 -42
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +63 -26
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +60 -69
- data/docs/fork_worker.md +31 -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 +2 -2
- 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/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +49 -12
- data/ext/puma_http11/http11_parser.c +46 -48
- data/ext/puma_http11/http11_parser.h +2 -2
- data/ext/puma_http11/http11_parser.java.rl +3 -3
- data/ext/puma_http11/http11_parser.rl +3 -3
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +250 -93
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
- data/ext/puma_http11/puma_http11.c +46 -57
- data/lib/puma/app/status.rb +52 -38
- data/lib/puma/binder.rb +232 -119
- data/lib/puma/cli.rb +33 -33
- data/lib/puma/client.rb +129 -88
- data/lib/puma/cluster/worker.rb +175 -0
- data/lib/puma/cluster/worker_handle.rb +97 -0
- data/lib/puma/cluster.rb +224 -231
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +112 -87
- data/lib/puma/const.rb +86 -91
- data/lib/puma/control_cli.rb +99 -79
- data/lib/puma/detect.rb +31 -2
- data/lib/puma/dsl.rb +426 -110
- data/lib/puma/error_logger.rb +112 -0
- data/lib/puma/events.rb +16 -115
- data/lib/puma/io_buffer.rb +44 -2
- data/lib/puma/jruby_restart.rb +2 -59
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +170 -148
- data/lib/puma/log_writer.rb +137 -0
- data/lib/puma/minissl/context_builder.rb +35 -19
- data/lib/puma/minissl.rb +213 -55
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +3 -12
- data/lib/puma/rack/builder.rb +5 -9
- data/lib/puma/rack_default.rb +1 -1
- data/lib/puma/reactor.rb +85 -369
- data/lib/puma/request.rb +644 -0
- data/lib/puma/runner.rb +86 -76
- data/lib/puma/server.rb +306 -793
- data/lib/puma/single.rb +18 -74
- data/lib/puma/state_file.rb +45 -8
- data/lib/puma/systemd.rb +47 -0
- data/lib/puma/thread_pool.rb +136 -68
- data/lib/puma/util.rb +21 -4
- data/lib/puma.rb +54 -7
- data/lib/rack/handler/puma.rb +11 -12
- data/tools/{docker/Dockerfile → Dockerfile} +1 -1
- metadata +31 -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
@@ -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
|
|
@@ -25,6 +26,9 @@ module Puma
|
|
25
26
|
|
26
27
|
class HttpParserError501 < IOError; end
|
27
28
|
|
29
|
+
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
30
|
+
|
31
|
+
|
28
32
|
# An instance of this class represents a unique request from a client.
|
29
33
|
# For example, this could be a web request from a browser or from CURL.
|
30
34
|
#
|
@@ -38,7 +42,7 @@ module Puma
|
|
38
42
|
# the header and body are fully buffered via the `try_to_finish` method.
|
39
43
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
40
44
|
#
|
41
|
-
class Client
|
45
|
+
class Client # :nodoc:
|
42
46
|
|
43
47
|
# this tests all values but the last, which must be chunked
|
44
48
|
ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
|
@@ -62,16 +66,14 @@ module Puma
|
|
62
66
|
def initialize(io, env=nil)
|
63
67
|
@io = io
|
64
68
|
@to_io = io.to_io
|
69
|
+
@io_buffer = IOBuffer.new
|
65
70
|
@proto_env = env
|
66
|
-
|
67
|
-
@env = nil
|
68
|
-
else
|
69
|
-
@env = env.dup
|
70
|
-
end
|
71
|
+
@env = env ? env.dup : nil
|
71
72
|
|
72
73
|
@parser = HttpParser.new
|
73
74
|
@parsed_bytes = 0
|
74
75
|
@read_header = true
|
76
|
+
@read_proxy = false
|
75
77
|
@ready = false
|
76
78
|
|
77
79
|
@body = nil
|
@@ -85,7 +87,10 @@ module Puma
|
|
85
87
|
@hijacked = false
|
86
88
|
|
87
89
|
@peerip = nil
|
90
|
+
@peer_family = nil
|
91
|
+
@listener = nil
|
88
92
|
@remote_addr_header = nil
|
93
|
+
@expect_proxy_proto = false
|
89
94
|
|
90
95
|
@body_remain = 0
|
91
96
|
|
@@ -93,14 +98,21 @@ module Puma
|
|
93
98
|
end
|
94
99
|
|
95
100
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
96
|
-
:tempfile
|
101
|
+
:tempfile, :io_buffer
|
97
102
|
|
98
103
|
attr_writer :peerip
|
99
104
|
|
100
|
-
attr_accessor :remote_addr_header
|
105
|
+
attr_accessor :remote_addr_header, :listener
|
101
106
|
|
102
107
|
def_delegators :@io, :closed?
|
103
108
|
|
109
|
+
# Test to see if io meets a bare minimum of functioning, @to_io needs to be
|
110
|
+
# used for MiniSSL::Socket
|
111
|
+
def io_ok?
|
112
|
+
@to_io.is_a?(::BasicSocket) && !closed?
|
113
|
+
end
|
114
|
+
|
115
|
+
# @!attribute [r] inspect
|
104
116
|
def inspect
|
105
117
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
106
118
|
end
|
@@ -112,27 +124,37 @@ module Puma
|
|
112
124
|
env[HIJACK_IO] ||= @io
|
113
125
|
end
|
114
126
|
|
127
|
+
# @!attribute [r] in_data_phase
|
115
128
|
def in_data_phase
|
116
|
-
|
129
|
+
!(@read_header || @read_proxy)
|
117
130
|
end
|
118
131
|
|
119
132
|
def set_timeout(val)
|
120
|
-
@timeout_at =
|
133
|
+
@timeout_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + val
|
134
|
+
end
|
135
|
+
|
136
|
+
# Number of seconds until the timeout elapses.
|
137
|
+
def timeout
|
138
|
+
[@timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
|
121
139
|
end
|
122
140
|
|
123
141
|
def reset(fast_check=true)
|
124
142
|
@parser.reset
|
143
|
+
@io_buffer.reset
|
125
144
|
@read_header = true
|
145
|
+
@read_proxy = !!@expect_proxy_proto
|
126
146
|
@env = @proto_env.dup
|
127
147
|
@body = nil
|
128
148
|
@tempfile = nil
|
129
149
|
@parsed_bytes = 0
|
130
150
|
@ready = false
|
131
151
|
@body_remain = 0
|
132
|
-
@peerip = nil
|
152
|
+
@peerip = nil if @remote_addr_header
|
133
153
|
@in_last_chunk = false
|
134
154
|
|
135
155
|
if @buffer
|
156
|
+
return false unless try_to_parse_proxy_protocol
|
157
|
+
|
136
158
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
137
159
|
|
138
160
|
if @parser.finished?
|
@@ -145,8 +167,7 @@ module Puma
|
|
145
167
|
return false
|
146
168
|
else
|
147
169
|
begin
|
148
|
-
if fast_check &&
|
149
|
-
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
170
|
+
if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
|
150
171
|
return try_to_finish
|
151
172
|
end
|
152
173
|
rescue IOError
|
@@ -159,19 +180,45 @@ module Puma
|
|
159
180
|
def close
|
160
181
|
begin
|
161
182
|
@io.close
|
162
|
-
rescue IOError
|
163
|
-
|
183
|
+
rescue IOError, Errno::EBADF
|
184
|
+
Puma::Util.purge_interrupt_queue
|
164
185
|
end
|
165
186
|
end
|
166
187
|
|
188
|
+
# If necessary, read the PROXY protocol from the buffer. Returns
|
189
|
+
# false if more data is needed.
|
190
|
+
def try_to_parse_proxy_protocol
|
191
|
+
if @read_proxy
|
192
|
+
if @expect_proxy_proto == :v1
|
193
|
+
if @buffer.include? "\r\n"
|
194
|
+
if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
|
195
|
+
if md[1]
|
196
|
+
@peerip = md[1].split(" ")[0]
|
197
|
+
end
|
198
|
+
@buffer = md.post_match
|
199
|
+
end
|
200
|
+
# if the buffer has a \r\n but doesn't have a PROXY protocol
|
201
|
+
# request, this is just HTTP from a non-PROXY client; move on
|
202
|
+
@read_proxy = false
|
203
|
+
return @buffer.size > 0
|
204
|
+
else
|
205
|
+
return false
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
true
|
210
|
+
end
|
211
|
+
|
167
212
|
def try_to_finish
|
168
|
-
return read_body
|
213
|
+
return read_body if in_data_phase
|
169
214
|
|
170
215
|
begin
|
171
216
|
data = @io.read_nonblock(CHUNK_SIZE)
|
172
217
|
rescue IO::WaitReadable
|
173
218
|
return false
|
174
|
-
rescue
|
219
|
+
rescue EOFError
|
220
|
+
# Swallow error, don't log
|
221
|
+
rescue SystemCallError, IOError
|
175
222
|
raise ConnectionError, "Connection error detected during read"
|
176
223
|
end
|
177
224
|
|
@@ -188,6 +235,8 @@ module Puma
|
|
188
235
|
@buffer = data
|
189
236
|
end
|
190
237
|
|
238
|
+
return false unless try_to_parse_proxy_protocol
|
239
|
+
|
191
240
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
192
241
|
|
193
242
|
if @parser.finished?
|
@@ -200,68 +249,20 @@ module Puma
|
|
200
249
|
false
|
201
250
|
end
|
202
251
|
|
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
|
252
|
+
def eagerly_finish
|
253
|
+
return true if @ready
|
254
|
+
return false unless @to_io.wait_readable(0)
|
255
|
+
try_to_finish
|
256
|
+
end
|
251
257
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
end
|
257
|
-
end # IS_JRUBY
|
258
|
+
def finish(timeout)
|
259
|
+
return if @ready
|
260
|
+
@to_io.wait_readable(timeout) || timeout! until try_to_finish
|
261
|
+
end
|
258
262
|
|
259
|
-
def
|
260
|
-
|
261
|
-
|
262
|
-
IO.select([@to_io], nil, nil)
|
263
|
-
end
|
264
|
-
true
|
263
|
+
def timeout!
|
264
|
+
write_error(408) if in_data_phase
|
265
|
+
raise ConnectionError
|
265
266
|
end
|
266
267
|
|
267
268
|
def write_error(status_code)
|
@@ -275,7 +276,7 @@ module Puma
|
|
275
276
|
return @peerip if @peerip
|
276
277
|
|
277
278
|
if @remote_addr_header
|
278
|
-
hdr = (@env[@remote_addr_header] ||
|
279
|
+
hdr = (@env[@remote_addr_header] || @io.peeraddr.last).split(/[\s,]/).first
|
279
280
|
@peerip = hdr
|
280
281
|
return hdr
|
281
282
|
end
|
@@ -283,10 +284,40 @@ module Puma
|
|
283
284
|
@peerip ||= @io.peeraddr.last
|
284
285
|
end
|
285
286
|
|
287
|
+
def peer_family
|
288
|
+
return @peer_family if @peer_family
|
289
|
+
|
290
|
+
@peer_family ||= begin
|
291
|
+
@io.local_address.afamily
|
292
|
+
rescue
|
293
|
+
Socket::AF_INET
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Returns true if the persistent connection can be closed immediately
|
298
|
+
# without waiting for the configured idle/shutdown timeout.
|
299
|
+
# @version 5.0.0
|
300
|
+
#
|
301
|
+
def can_close?
|
302
|
+
# Allow connection to close if we're not in the middle of parsing a request.
|
303
|
+
@parsed_bytes == 0
|
304
|
+
end
|
305
|
+
|
306
|
+
def expect_proxy_proto=(val)
|
307
|
+
if val
|
308
|
+
if @read_header
|
309
|
+
@read_proxy = true
|
310
|
+
end
|
311
|
+
else
|
312
|
+
@read_proxy = false
|
313
|
+
end
|
314
|
+
@expect_proxy_proto = val
|
315
|
+
end
|
316
|
+
|
286
317
|
private
|
287
318
|
|
288
319
|
def setup_body
|
289
|
-
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :
|
320
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
290
321
|
|
291
322
|
if @env[HTTP_EXPECT] == CONTINUE
|
292
323
|
# TODO allow a hook here to check the headers before
|
@@ -330,7 +361,7 @@ module Puma
|
|
330
361
|
|
331
362
|
if cl
|
332
363
|
# cannot contain characters that are not \d
|
333
|
-
if cl
|
364
|
+
if CONTENT_LENGTH_VALUE_INVALID.match? cl
|
334
365
|
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
335
366
|
end
|
336
367
|
else
|
@@ -351,6 +382,7 @@ module Puma
|
|
351
382
|
|
352
383
|
if remain > MAX_BODY
|
353
384
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
385
|
+
@body.unlink
|
354
386
|
@body.binmode
|
355
387
|
@tempfile = @body
|
356
388
|
else
|
@@ -363,7 +395,7 @@ module Puma
|
|
363
395
|
|
364
396
|
@body_remain = remain
|
365
397
|
|
366
|
-
|
398
|
+
false
|
367
399
|
end
|
368
400
|
|
369
401
|
def read_body
|
@@ -430,7 +462,7 @@ module Puma
|
|
430
462
|
end
|
431
463
|
|
432
464
|
if decode_chunk(chunk)
|
433
|
-
@env[CONTENT_LENGTH] = @chunked_content_length
|
465
|
+
@env[CONTENT_LENGTH] = @chunked_content_length.to_s
|
434
466
|
return true
|
435
467
|
end
|
436
468
|
end
|
@@ -442,17 +474,18 @@ module Puma
|
|
442
474
|
@prev_chunk = ""
|
443
475
|
|
444
476
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
477
|
+
@body.unlink
|
445
478
|
@body.binmode
|
446
479
|
@tempfile = @body
|
447
|
-
|
448
480
|
@chunked_content_length = 0
|
449
481
|
|
450
482
|
if decode_chunk(body)
|
451
|
-
@env[CONTENT_LENGTH] = @chunked_content_length
|
483
|
+
@env[CONTENT_LENGTH] = @chunked_content_length.to_s
|
452
484
|
return true
|
453
485
|
end
|
454
486
|
end
|
455
487
|
|
488
|
+
# @version 5.0.0
|
456
489
|
def write_chunk(str)
|
457
490
|
@chunked_content_length += @body.write(str)
|
458
491
|
end
|
@@ -466,7 +499,15 @@ module Puma
|
|
466
499
|
chunk = chunk[@partial_part_left..-1]
|
467
500
|
@partial_part_left = 0
|
468
501
|
else
|
469
|
-
|
502
|
+
if @partial_part_left > 2
|
503
|
+
if @partial_part_left == chunk.size + 1
|
504
|
+
# Don't include the last \r
|
505
|
+
write_chunk(chunk[0..(@partial_part_left-3)])
|
506
|
+
else
|
507
|
+
# don't include the last \r\n
|
508
|
+
write_chunk(chunk)
|
509
|
+
end
|
510
|
+
end
|
470
511
|
@partial_part_left -= chunk.size
|
471
512
|
return false
|
472
513
|
end
|
@@ -485,7 +526,7 @@ module Puma
|
|
485
526
|
# Puma doesn't process chunk extensions, but should parse if they're
|
486
527
|
# present, which is the reason for the semicolon regex
|
487
528
|
chunk_hex = line.strip[/\A[^;]+/]
|
488
|
-
if chunk_hex
|
529
|
+
if CHUNK_SIZE_INVALID.match? chunk_hex
|
489
530
|
raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
|
490
531
|
end
|
491
532
|
len = chunk_hex.to_i(16)
|
@@ -548,7 +589,7 @@ module Puma
|
|
548
589
|
|
549
590
|
def set_ready
|
550
591
|
if @body_read_start
|
551
|
-
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :
|
592
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - @body_read_start
|
552
593
|
end
|
553
594
|
@requests_served += 1
|
554
595
|
@ready = true
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Puma
|
4
|
+
class Cluster < Puma::Runner
|
5
|
+
#—————————————————————— DO NOT USE — this class is for internal use only ———
|
6
|
+
|
7
|
+
|
8
|
+
# This class is instantiated by the `Puma::Cluster` and represents a single
|
9
|
+
# worker process.
|
10
|
+
#
|
11
|
+
# At the core of this class is running an instance of `Puma::Server` which
|
12
|
+
# gets created via the `start_server` method from the `Puma::Runner` class
|
13
|
+
# that this inherits from.
|
14
|
+
class Worker < Puma::Runner # :nodoc:
|
15
|
+
attr_reader :index, :master
|
16
|
+
|
17
|
+
def initialize(index:, master:, launcher:, pipes:, server: nil)
|
18
|
+
super(launcher)
|
19
|
+
|
20
|
+
@index = index
|
21
|
+
@master = master
|
22
|
+
@check_pipe = pipes[:check_pipe]
|
23
|
+
@worker_write = pipes[:worker_write]
|
24
|
+
@fork_pipe = pipes[:fork_pipe]
|
25
|
+
@wakeup = pipes[:wakeup]
|
26
|
+
@server = server
|
27
|
+
@hook_data = {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def run
|
31
|
+
title = "puma: cluster worker #{index}: #{master}"
|
32
|
+
title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
33
|
+
$0 = title
|
34
|
+
|
35
|
+
Signal.trap "SIGINT", "IGNORE"
|
36
|
+
Signal.trap "SIGCHLD", "DEFAULT"
|
37
|
+
|
38
|
+
Thread.new do
|
39
|
+
Puma.set_thread_name "wrkr check"
|
40
|
+
@check_pipe.wait_readable
|
41
|
+
log "! Detected parent died, dying"
|
42
|
+
exit! 1
|
43
|
+
end
|
44
|
+
|
45
|
+
# If we're not running under a Bundler context, then
|
46
|
+
# report the info about the context we will be using
|
47
|
+
if !ENV['BUNDLE_GEMFILE']
|
48
|
+
if File.exist?("Gemfile")
|
49
|
+
log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
|
50
|
+
elsif File.exist?("gems.rb")
|
51
|
+
log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Invoke any worker boot hooks so they can get
|
56
|
+
# things in shape before booting the app.
|
57
|
+
@config.run_hooks(:before_worker_boot, index, @log_writer, @hook_data)
|
58
|
+
|
59
|
+
begin
|
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
|
+
|
68
|
+
restart_server = Queue.new << true << false
|
69
|
+
|
70
|
+
fork_worker = @options[:fork_worker] && index == 0
|
71
|
+
|
72
|
+
if fork_worker
|
73
|
+
restart_server.clear
|
74
|
+
worker_pids = []
|
75
|
+
Signal.trap "SIGCHLD" do
|
76
|
+
wakeup! if worker_pids.reject! do |p|
|
77
|
+
Process.wait(p, Process::WNOHANG) rescue true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
Thread.new do
|
82
|
+
Puma.set_thread_name "wrkr fork"
|
83
|
+
while (idx = @fork_pipe.gets)
|
84
|
+
idx = idx.to_i
|
85
|
+
if idx == -1 # stop server
|
86
|
+
if restart_server.length > 0
|
87
|
+
restart_server.clear
|
88
|
+
server.begin_restart(true)
|
89
|
+
@config.run_hooks(:before_refork, nil, @log_writer, @hook_data)
|
90
|
+
end
|
91
|
+
elsif idx == 0 # restart server
|
92
|
+
restart_server << true << false
|
93
|
+
else # fork worker
|
94
|
+
worker_pids << pid = spawn_worker(idx)
|
95
|
+
@worker_write << "f#{pid}:#{idx}\n" rescue nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
Signal.trap "SIGTERM" do
|
102
|
+
@worker_write << "e#{Process.pid}\n" rescue nil
|
103
|
+
restart_server.clear
|
104
|
+
server.stop
|
105
|
+
restart_server << false
|
106
|
+
end
|
107
|
+
|
108
|
+
begin
|
109
|
+
@worker_write << "b#{Process.pid}:#{index}\n"
|
110
|
+
rescue SystemCallError, IOError
|
111
|
+
Puma::Util.purge_interrupt_queue
|
112
|
+
STDERR.puts "Master seems to have exited, exiting."
|
113
|
+
return
|
114
|
+
end
|
115
|
+
|
116
|
+
while restart_server.pop
|
117
|
+
server_thread = server.run
|
118
|
+
stat_thread ||= Thread.new(@worker_write) do |io|
|
119
|
+
Puma.set_thread_name "stat pld"
|
120
|
+
base_payload = "p#{Process.pid}"
|
121
|
+
|
122
|
+
while true
|
123
|
+
begin
|
124
|
+
b = server.backlog || 0
|
125
|
+
r = server.running || 0
|
126
|
+
t = server.pool_capacity || 0
|
127
|
+
m = server.max_threads || 0
|
128
|
+
rc = server.requests_count || 0
|
129
|
+
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
|
130
|
+
io << payload
|
131
|
+
rescue IOError
|
132
|
+
Puma::Util.purge_interrupt_queue
|
133
|
+
break
|
134
|
+
end
|
135
|
+
sleep @options[:worker_check_interval]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
server_thread.join
|
139
|
+
end
|
140
|
+
|
141
|
+
# Invoke any worker shutdown hooks so they can prevent the worker
|
142
|
+
# exiting until any background operations are completed
|
143
|
+
@config.run_hooks(:before_worker_shutdown, index, @log_writer, @hook_data)
|
144
|
+
ensure
|
145
|
+
@worker_write << "t#{Process.pid}\n" rescue nil
|
146
|
+
@worker_write.close
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def spawn_worker(idx)
|
152
|
+
@config.run_hooks(:before_worker_fork, idx, @log_writer, @hook_data)
|
153
|
+
|
154
|
+
pid = fork do
|
155
|
+
new_worker = Worker.new index: idx,
|
156
|
+
master: master,
|
157
|
+
launcher: @launcher,
|
158
|
+
pipes: { check_pipe: @check_pipe,
|
159
|
+
worker_write: @worker_write },
|
160
|
+
server: @server
|
161
|
+
new_worker.run
|
162
|
+
end
|
163
|
+
|
164
|
+
if !pid
|
165
|
+
log "! Complete inability to spawn new workers detected"
|
166
|
+
log "! Seppuku is the only choice."
|
167
|
+
exit! 1
|
168
|
+
end
|
169
|
+
|
170
|
+
@config.run_hooks(:after_worker_fork, idx, @log_writer, @hook_data)
|
171
|
+
pid
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Puma
|
4
|
+
class Cluster < Runner
|
5
|
+
#—————————————————————— DO NOT USE — this class is for internal use only ———
|
6
|
+
|
7
|
+
|
8
|
+
# This class represents a worker process from the perspective of the puma
|
9
|
+
# master process. It contains information about the process and its health
|
10
|
+
# and it exposes methods to control the process via IPC. It does not
|
11
|
+
# include the actual logic executed by the worker process itself. For that,
|
12
|
+
# see Puma::Cluster::Worker.
|
13
|
+
class WorkerHandle # :nodoc:
|
14
|
+
def initialize(idx, pid, phase, options)
|
15
|
+
@index = idx
|
16
|
+
@pid = pid
|
17
|
+
@phase = phase
|
18
|
+
@stage = :started
|
19
|
+
@signal = "TERM"
|
20
|
+
@options = options
|
21
|
+
@first_term_sent = nil
|
22
|
+
@started_at = Time.now
|
23
|
+
@last_checkin = Time.now
|
24
|
+
@last_status = {}
|
25
|
+
@term = false
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
29
|
+
|
30
|
+
# @version 5.0.0
|
31
|
+
attr_writer :pid, :phase
|
32
|
+
|
33
|
+
def booted?
|
34
|
+
@stage == :booted
|
35
|
+
end
|
36
|
+
|
37
|
+
def uptime
|
38
|
+
Time.now - started_at
|
39
|
+
end
|
40
|
+
|
41
|
+
def boot!
|
42
|
+
@last_checkin = Time.now
|
43
|
+
@stage = :booted
|
44
|
+
end
|
45
|
+
|
46
|
+
def term!
|
47
|
+
@term = true
|
48
|
+
end
|
49
|
+
|
50
|
+
def term?
|
51
|
+
@term
|
52
|
+
end
|
53
|
+
|
54
|
+
def ping!(status)
|
55
|
+
@last_checkin = Time.now
|
56
|
+
captures = status.match(/{ "backlog":(?<backlog>\d*), "running":(?<running>\d*), "pool_capacity":(?<pool_capacity>\d*), "max_threads": (?<max_threads>\d*), "requests_count": (?<requests_count>\d*) }/)
|
57
|
+
@last_status = captures.names.inject({}) do |hash, key|
|
58
|
+
hash[key.to_sym] = captures[key].to_i
|
59
|
+
hash
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @see Puma::Cluster#check_workers
|
64
|
+
# @version 5.0.0
|
65
|
+
def ping_timeout
|
66
|
+
@last_checkin +
|
67
|
+
(booted? ?
|
68
|
+
@options[:worker_timeout] :
|
69
|
+
@options[:worker_boot_timeout]
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
def term
|
74
|
+
begin
|
75
|
+
if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
|
76
|
+
@signal = "KILL"
|
77
|
+
else
|
78
|
+
@term ||= true
|
79
|
+
@first_term_sent ||= Time.now
|
80
|
+
end
|
81
|
+
Process.kill @signal, @pid if @pid
|
82
|
+
rescue Errno::ESRCH
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def kill
|
87
|
+
@signal = 'KILL'
|
88
|
+
term
|
89
|
+
end
|
90
|
+
|
91
|
+
def hup
|
92
|
+
Process.kill "HUP", @pid
|
93
|
+
rescue Errno::ESRCH
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|