puma 5.0.0.beta1-java → 5.0.3-java
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 +1188 -559
- data/README.md +15 -8
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +3 -3
- data/docs/deployment.md +10 -7
- data/docs/jungle/README.md +0 -4
- data/docs/jungle/rc.d/puma +2 -2
- data/docs/nginx.md +1 -1
- data/docs/restart.md +46 -23
- data/docs/signals.md +7 -7
- data/docs/systemd.md +1 -1
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/http11_parser.c +3 -1
- data/ext/puma_http11/http11_parser.rl +3 -1
- data/ext/puma_http11/mini_ssl.c +53 -38
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
- data/ext/puma_http11/puma_http11.c +22 -11
- data/lib/puma.rb +16 -0
- data/lib/puma/app/status.rb +47 -44
- data/lib/puma/binder.rb +40 -12
- data/lib/puma/client.rb +68 -82
- data/lib/puma/cluster.rb +30 -187
- data/lib/puma/cluster/worker.rb +170 -0
- data/lib/puma/cluster/worker_handle.rb +83 -0
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +9 -7
- data/lib/puma/const.rb +2 -1
- data/lib/puma/control_cli.rb +2 -0
- data/lib/puma/detect.rb +9 -0
- data/lib/puma/dsl.rb +77 -39
- data/lib/puma/error_logger.rb +97 -0
- data/lib/puma/events.rb +37 -31
- data/lib/puma/launcher.rb +20 -10
- data/lib/puma/minissl.rb +55 -10
- data/lib/puma/minissl/context_builder.rb +0 -3
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/reactor.rb +77 -373
- data/lib/puma/request.rb +438 -0
- data/lib/puma/runner.rb +7 -19
- data/lib/puma/server.rb +229 -506
- data/lib/puma/single.rb +3 -2
- data/lib/puma/state_file.rb +1 -1
- data/lib/puma/thread_pool.rb +32 -5
- data/lib/puma/util.rb +12 -0
- metadata +12 -10
- data/docs/jungle/upstart/README.md +0 -61
- data/docs/jungle/upstart/puma-manager.conf +0 -31
- data/docs/jungle/upstart/puma.conf +0 -69
- data/lib/puma/accept_nonblock.rb +0 -29
data/lib/puma/binder.rb
CHANGED
@@ -5,15 +5,30 @@ require 'socket'
|
|
5
5
|
|
6
6
|
require 'puma/const'
|
7
7
|
require 'puma/util'
|
8
|
-
require 'puma/
|
8
|
+
require 'puma/configuration'
|
9
9
|
|
10
10
|
module Puma
|
11
|
+
|
12
|
+
if HAS_SSL
|
13
|
+
require 'puma/minissl'
|
14
|
+
require 'puma/minissl/context_builder'
|
15
|
+
|
16
|
+
# Odd bug in 'pure Ruby' nio4r verion 2.5.2, which installs with Ruby 2.3.
|
17
|
+
# NIO doesn't create any OpenSSL objects, but it rescues an OpenSSL error.
|
18
|
+
# The bug was that it did not require openssl.
|
19
|
+
# @todo remove when Ruby 2.3 support is dropped
|
20
|
+
#
|
21
|
+
if windows? && RbConfig::CONFIG['ruby_version'] == '2.3.0'
|
22
|
+
require 'openssl'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
11
26
|
class Binder
|
12
27
|
include Puma::Const
|
13
28
|
|
14
29
|
RACK_VERSION = [1,6].freeze
|
15
30
|
|
16
|
-
def initialize(events)
|
31
|
+
def initialize(events, conf = Configuration.new)
|
17
32
|
@events = events
|
18
33
|
@listeners = []
|
19
34
|
@inherited_fds = {}
|
@@ -23,8 +38,8 @@ module Puma
|
|
23
38
|
@proto_env = {
|
24
39
|
"rack.version".freeze => RACK_VERSION,
|
25
40
|
"rack.errors".freeze => events.stderr,
|
26
|
-
"rack.multithread".freeze =>
|
27
|
-
"rack.multiprocess".freeze =>
|
41
|
+
"rack.multithread".freeze => conf.options[:max_threads] > 1,
|
42
|
+
"rack.multiprocess".freeze => conf.options[:workers] >= 1,
|
28
43
|
"rack.run_once".freeze => false,
|
29
44
|
"SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
|
30
45
|
|
@@ -43,7 +58,12 @@ module Puma
|
|
43
58
|
@ios = []
|
44
59
|
end
|
45
60
|
|
46
|
-
attr_reader :ios
|
61
|
+
attr_reader :ios
|
62
|
+
|
63
|
+
# @version 5.0.0
|
64
|
+
attr_reader :activated_sockets, :envs, :inherited_fds, :listeners, :proto_env, :unix_paths
|
65
|
+
|
66
|
+
# @version 5.0.0
|
47
67
|
attr_writer :ios, :listeners
|
48
68
|
|
49
69
|
def env(sock)
|
@@ -54,10 +74,13 @@ module Puma
|
|
54
74
|
@ios.each { |i| i.close }
|
55
75
|
end
|
56
76
|
|
77
|
+
# @!attribute [r] connected_ports
|
78
|
+
# @version 5.0.0
|
57
79
|
def connected_ports
|
58
80
|
ios.map { |io| io.addr[1] }.uniq
|
59
81
|
end
|
60
82
|
|
83
|
+
# @version 5.0.0
|
61
84
|
def create_inherited_fds(env_hash)
|
62
85
|
env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
|
63
86
|
fd, url = v.split(":", 2)
|
@@ -68,7 +91,9 @@ module Puma
|
|
68
91
|
# systemd socket activation.
|
69
92
|
# LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
|
70
93
|
# LISTEN_PID = PID of the service process, aka us
|
71
|
-
# see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
|
94
|
+
# @see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
|
95
|
+
# @version 5.0.0
|
96
|
+
#
|
72
97
|
def create_activated_fds(env_hash)
|
73
98
|
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
|
74
99
|
env_hash['LISTEN_FDS'].to_i.times do |index|
|
@@ -113,7 +138,7 @@ module Puma
|
|
113
138
|
i.local_address.ip_unpack.join(':')
|
114
139
|
end
|
115
140
|
|
116
|
-
logger.log "* #{log_msg} on
|
141
|
+
logger.log "* #{log_msg} on http://#{addr}"
|
117
142
|
end
|
118
143
|
end
|
119
144
|
|
@@ -154,6 +179,9 @@ module Puma
|
|
154
179
|
|
155
180
|
@listeners << [str, io]
|
156
181
|
when "ssl"
|
182
|
+
|
183
|
+
raise "Puma compiled without SSL support" unless HAS_SSL
|
184
|
+
|
157
185
|
params = Util.parse_query uri.query
|
158
186
|
ctx = MiniSSL::ContextBuilder.new(params, @events).context
|
159
187
|
|
@@ -244,9 +272,8 @@ module Puma
|
|
244
272
|
|
245
273
|
def add_ssl_listener(host, port, ctx,
|
246
274
|
optimize_for_latency=true, backlog=1024)
|
247
|
-
require 'puma/minissl'
|
248
275
|
|
249
|
-
|
276
|
+
raise "Puma compiled without SSL support" unless HAS_SSL
|
250
277
|
|
251
278
|
if host == "localhost"
|
252
279
|
loopback_addresses.each do |addr|
|
@@ -263,7 +290,6 @@ module Puma
|
|
263
290
|
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
264
291
|
s.listen backlog
|
265
292
|
|
266
|
-
|
267
293
|
ssl = MiniSSL::Server.new s, ctx
|
268
294
|
env = @proto_env.dup
|
269
295
|
env[HTTPS_KEY] = HTTPS
|
@@ -274,8 +300,7 @@ module Puma
|
|
274
300
|
end
|
275
301
|
|
276
302
|
def inherit_ssl_listener(fd, ctx)
|
277
|
-
|
278
|
-
MiniSSL.check
|
303
|
+
raise "Puma compiled without SSL support" unless HAS_SSL
|
279
304
|
|
280
305
|
if fd.kind_of? TCPServer
|
281
306
|
s = fd
|
@@ -366,6 +391,7 @@ module Puma
|
|
366
391
|
redirects
|
367
392
|
end
|
368
393
|
|
394
|
+
# @version 5.0.0
|
369
395
|
def redirects_for_restart_env
|
370
396
|
listeners.each_with_object({}).with_index do |(listen, memo), i|
|
371
397
|
memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
|
@@ -374,12 +400,14 @@ module Puma
|
|
374
400
|
|
375
401
|
private
|
376
402
|
|
403
|
+
# @!attribute [r] loopback_addresses
|
377
404
|
def loopback_addresses
|
378
405
|
Socket.ip_address_list.select do |addrinfo|
|
379
406
|
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
380
407
|
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
381
408
|
end
|
382
409
|
|
410
|
+
# @version 5.0.0
|
383
411
|
def socket_activation_fd(int)
|
384
412
|
int + 3 # 3 is the magic number you add to follow the SA protocol
|
385
413
|
end
|
data/lib/puma/client.rb
CHANGED
@@ -85,6 +85,13 @@ module Puma
|
|
85
85
|
|
86
86
|
def_delegators :@io, :closed?
|
87
87
|
|
88
|
+
# Test to see if io meets a bare minimum of functioning, @to_io needs to be
|
89
|
+
# used for MiniSSL::Socket
|
90
|
+
def io_ok?
|
91
|
+
@to_io.is_a?(::BasicSocket) && !closed?
|
92
|
+
end
|
93
|
+
|
94
|
+
# @!attribute [r] inspect
|
88
95
|
def inspect
|
89
96
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
90
97
|
end
|
@@ -96,12 +103,18 @@ module Puma
|
|
96
103
|
env[HIJACK_IO] ||= @io
|
97
104
|
end
|
98
105
|
|
106
|
+
# @!attribute [r] in_data_phase
|
99
107
|
def in_data_phase
|
100
108
|
!@read_header
|
101
109
|
end
|
102
110
|
|
103
111
|
def set_timeout(val)
|
104
|
-
@timeout_at =
|
112
|
+
@timeout_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + val
|
113
|
+
end
|
114
|
+
|
115
|
+
# Number of seconds until the timeout elapses.
|
116
|
+
def timeout
|
117
|
+
[@timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
|
105
118
|
end
|
106
119
|
|
107
120
|
def reset(fast_check=true)
|
@@ -155,7 +168,9 @@ module Puma
|
|
155
168
|
data = @io.read_nonblock(CHUNK_SIZE)
|
156
169
|
rescue IO::WaitReadable
|
157
170
|
return false
|
158
|
-
rescue
|
171
|
+
rescue EOFError
|
172
|
+
# Swallow error, don't log
|
173
|
+
rescue SystemCallError, IOError
|
159
174
|
raise ConnectionError, "Connection error detected during read"
|
160
175
|
end
|
161
176
|
|
@@ -184,79 +199,20 @@ module Puma
|
|
184
199
|
false
|
185
200
|
end
|
186
201
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
data = @io.sysread_nonblock(CHUNK_SIZE)
|
193
|
-
rescue OpenSSL::SSL::SSLError => e
|
194
|
-
return false if e.kind_of? IO::WaitReadable
|
195
|
-
raise e
|
196
|
-
end
|
197
|
-
|
198
|
-
# No data means a closed socket
|
199
|
-
unless data
|
200
|
-
@buffer = nil
|
201
|
-
set_ready
|
202
|
-
raise EOFError
|
203
|
-
end
|
204
|
-
|
205
|
-
if @buffer
|
206
|
-
@buffer << data
|
207
|
-
else
|
208
|
-
@buffer = data
|
209
|
-
end
|
210
|
-
|
211
|
-
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
212
|
-
|
213
|
-
if @parser.finished?
|
214
|
-
return setup_body
|
215
|
-
elsif @parsed_bytes >= MAX_HEADER
|
216
|
-
raise HttpParserError,
|
217
|
-
"HEADER is longer than allowed, aborting client early."
|
218
|
-
end
|
219
|
-
|
220
|
-
false
|
221
|
-
end
|
222
|
-
|
223
|
-
def eagerly_finish
|
224
|
-
return true if @ready
|
225
|
-
|
226
|
-
if @io.kind_of? OpenSSL::SSL::SSLSocket
|
227
|
-
return true if jruby_start_try_to_finish
|
228
|
-
end
|
229
|
-
|
230
|
-
return false unless IO.select([@to_io], nil, nil, 0)
|
231
|
-
try_to_finish
|
232
|
-
end
|
233
|
-
|
234
|
-
else
|
235
|
-
|
236
|
-
def eagerly_finish
|
237
|
-
return true if @ready
|
238
|
-
return false unless IO.select([@to_io], nil, nil, 0)
|
239
|
-
try_to_finish
|
240
|
-
end
|
241
|
-
|
242
|
-
# For documentation, see https://github.com/puma/puma/issues/1754
|
243
|
-
send(:alias_method, :jruby_eagerly_finish, :eagerly_finish)
|
244
|
-
end # IS_JRUBY
|
202
|
+
def eagerly_finish
|
203
|
+
return true if @ready
|
204
|
+
return false unless IO.select([@to_io], nil, nil, 0)
|
205
|
+
try_to_finish
|
206
|
+
end
|
245
207
|
|
246
208
|
def finish(timeout)
|
247
|
-
return
|
248
|
-
until try_to_finish
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
unless can_read
|
255
|
-
write_error(408) if in_data_phase
|
256
|
-
raise ConnectionError
|
257
|
-
end
|
258
|
-
end
|
259
|
-
true
|
209
|
+
return if @ready
|
210
|
+
IO.select([@to_io], nil, nil, timeout) || timeout! until try_to_finish
|
211
|
+
end
|
212
|
+
|
213
|
+
def timeout!
|
214
|
+
write_error(408) if in_data_phase
|
215
|
+
raise ConnectionError
|
260
216
|
end
|
261
217
|
|
262
218
|
def write_error(status_code)
|
@@ -280,6 +236,8 @@ module Puma
|
|
280
236
|
|
281
237
|
# Returns true if the persistent connection can be closed immediately
|
282
238
|
# without waiting for the configured idle/shutdown timeout.
|
239
|
+
# @version 5.0.0
|
240
|
+
#
|
283
241
|
def can_close?
|
284
242
|
# Allow connection to close if it's received at least one full request
|
285
243
|
# and hasn't received any data for a future request.
|
@@ -308,8 +266,16 @@ module Puma
|
|
308
266
|
|
309
267
|
te = @env[TRANSFER_ENCODING2]
|
310
268
|
|
311
|
-
if te
|
312
|
-
|
269
|
+
if te
|
270
|
+
if te.include?(",")
|
271
|
+
te.split(",").each do |part|
|
272
|
+
if CHUNKED.casecmp(part.strip) == 0
|
273
|
+
return setup_chunked_body(body)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
elsif CHUNKED.casecmp(te) == 0
|
277
|
+
return setup_chunked_body(body)
|
278
|
+
end
|
313
279
|
end
|
314
280
|
|
315
281
|
@chunked_body = false
|
@@ -412,7 +378,10 @@ module Puma
|
|
412
378
|
raise EOFError
|
413
379
|
end
|
414
380
|
|
415
|
-
|
381
|
+
if decode_chunk(chunk)
|
382
|
+
@env[CONTENT_LENGTH] = @chunked_content_length
|
383
|
+
return true
|
384
|
+
end
|
416
385
|
end
|
417
386
|
end
|
418
387
|
|
@@ -424,20 +393,37 @@ module Puma
|
|
424
393
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
425
394
|
@body.binmode
|
426
395
|
@tempfile = @body
|
396
|
+
@chunked_content_length = 0
|
397
|
+
|
398
|
+
if decode_chunk(body)
|
399
|
+
@env[CONTENT_LENGTH] = @chunked_content_length
|
400
|
+
return true
|
401
|
+
end
|
402
|
+
end
|
427
403
|
|
428
|
-
|
404
|
+
# @version 5.0.0
|
405
|
+
def write_chunk(str)
|
406
|
+
@chunked_content_length += @body.write(str)
|
429
407
|
end
|
430
408
|
|
431
409
|
def decode_chunk(chunk)
|
432
410
|
if @partial_part_left > 0
|
433
411
|
if @partial_part_left <= chunk.size
|
434
412
|
if @partial_part_left > 2
|
435
|
-
|
413
|
+
write_chunk(chunk[0..(@partial_part_left-3)]) # skip the \r\n
|
436
414
|
end
|
437
415
|
chunk = chunk[@partial_part_left..-1]
|
438
416
|
@partial_part_left = 0
|
439
417
|
else
|
440
|
-
|
418
|
+
if @partial_part_left > 2
|
419
|
+
if @partial_part_left == chunk.size + 1
|
420
|
+
# Don't include the last \r
|
421
|
+
write_chunk(chunk[0..(@partial_part_left-3)])
|
422
|
+
else
|
423
|
+
# don't include the last \r\n
|
424
|
+
write_chunk(chunk)
|
425
|
+
end
|
426
|
+
end
|
441
427
|
@partial_part_left -= chunk.size
|
442
428
|
return false
|
443
429
|
end
|
@@ -484,12 +470,12 @@ module Puma
|
|
484
470
|
|
485
471
|
case
|
486
472
|
when got == len
|
487
|
-
|
473
|
+
write_chunk(part[0..-3]) # to skip the ending \r\n
|
488
474
|
when got <= len - 2
|
489
|
-
|
475
|
+
write_chunk(part)
|
490
476
|
@partial_part_left = len - part.size
|
491
477
|
when got == len - 1 # edge where we get just \r but not \n
|
492
|
-
|
478
|
+
write_chunk(part[0..-2])
|
493
479
|
@partial_part_left = len - part.size
|
494
480
|
end
|
495
481
|
else
|
data/lib/puma/cluster.rb
CHANGED
@@ -3,19 +3,16 @@
|
|
3
3
|
require 'puma/runner'
|
4
4
|
require 'puma/util'
|
5
5
|
require 'puma/plugin'
|
6
|
+
require 'puma/cluster/worker_handle'
|
7
|
+
require 'puma/cluster/worker'
|
6
8
|
|
7
9
|
require 'time'
|
8
|
-
require 'json'
|
9
10
|
|
10
11
|
module Puma
|
11
12
|
# This class is instantiated by the `Puma::Launcher` and used
|
12
13
|
# to boot and serve a Ruby application when puma "workers" are needed
|
13
14
|
# i.e. when using multi-processes. For example `$ puma -w 5`
|
14
15
|
#
|
15
|
-
# At the core of this class is running an instance of `Puma::Server` which
|
16
|
-
# gets created via the `start_server` method from the `Puma::Runner` class
|
17
|
-
# that this inherits from.
|
18
|
-
#
|
19
16
|
# An instance of this class will spawn the number of processes passed in
|
20
17
|
# via the `spawn_workers` method call. Each worker will have it's own
|
21
18
|
# instance of a `Puma::Server`.
|
@@ -62,74 +59,6 @@ module Puma
|
|
62
59
|
@workers.each { |x| x.hup }
|
63
60
|
end
|
64
61
|
|
65
|
-
class Worker
|
66
|
-
def initialize(idx, pid, phase, options)
|
67
|
-
@index = idx
|
68
|
-
@pid = pid
|
69
|
-
@phase = phase
|
70
|
-
@stage = :started
|
71
|
-
@signal = "TERM"
|
72
|
-
@options = options
|
73
|
-
@first_term_sent = nil
|
74
|
-
@started_at = Time.now
|
75
|
-
@last_checkin = Time.now
|
76
|
-
@last_status = {}
|
77
|
-
@term = false
|
78
|
-
end
|
79
|
-
|
80
|
-
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
81
|
-
attr_writer :pid, :phase
|
82
|
-
|
83
|
-
def booted?
|
84
|
-
@stage == :booted
|
85
|
-
end
|
86
|
-
|
87
|
-
def boot!
|
88
|
-
@last_checkin = Time.now
|
89
|
-
@stage = :booted
|
90
|
-
end
|
91
|
-
|
92
|
-
def term?
|
93
|
-
@term
|
94
|
-
end
|
95
|
-
|
96
|
-
def ping!(status)
|
97
|
-
@last_checkin = Time.now
|
98
|
-
@last_status = JSON.parse(status, symbolize_names: true)
|
99
|
-
end
|
100
|
-
|
101
|
-
def ping_timeout
|
102
|
-
@last_checkin +
|
103
|
-
(booted? ?
|
104
|
-
@options[:worker_timeout] :
|
105
|
-
@options[:worker_boot_timeout]
|
106
|
-
)
|
107
|
-
end
|
108
|
-
|
109
|
-
def term
|
110
|
-
begin
|
111
|
-
if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
|
112
|
-
@signal = "KILL"
|
113
|
-
else
|
114
|
-
@term ||= true
|
115
|
-
@first_term_sent ||= Time.now
|
116
|
-
end
|
117
|
-
Process.kill @signal, @pid if @pid
|
118
|
-
rescue Errno::ESRCH
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
def kill
|
123
|
-
@signal = 'KILL'
|
124
|
-
term
|
125
|
-
end
|
126
|
-
|
127
|
-
def hup
|
128
|
-
Process.kill "HUP", @pid
|
129
|
-
rescue Errno::ESRCH
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
62
|
def spawn_workers
|
134
63
|
diff = @options[:workers] - @workers.size
|
135
64
|
return if diff < 1
|
@@ -150,7 +79,7 @@ module Puma
|
|
150
79
|
end
|
151
80
|
|
152
81
|
debug "Spawned worker: #{pid}"
|
153
|
-
@workers <<
|
82
|
+
@workers << WorkerHandle.new(idx, pid, @phase, @options)
|
154
83
|
end
|
155
84
|
|
156
85
|
if @options[:fork_worker] &&
|
@@ -160,6 +89,7 @@ module Puma
|
|
160
89
|
end
|
161
90
|
end
|
162
91
|
|
92
|
+
# @version 5.0.0
|
163
93
|
def spawn_worker(idx, master)
|
164
94
|
@launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
|
165
95
|
|
@@ -189,6 +119,7 @@ module Puma
|
|
189
119
|
end
|
190
120
|
end
|
191
121
|
|
122
|
+
# @!attribute [r] next_worker_index
|
192
123
|
def next_worker_index
|
193
124
|
all_positions = 0...@options[:workers]
|
194
125
|
occupied_positions = @workers.map { |w| w.index }
|
@@ -243,109 +174,23 @@ module Puma
|
|
243
174
|
end
|
244
175
|
|
245
176
|
def worker(index, master)
|
246
|
-
title = "puma: cluster worker #{index}: #{master}"
|
247
|
-
title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
248
|
-
$0 = title
|
249
|
-
|
250
|
-
Signal.trap "SIGINT", "IGNORE"
|
251
|
-
|
252
|
-
fork_worker = @options[:fork_worker] && index == 0
|
253
|
-
|
254
177
|
@workers = []
|
255
|
-
if !@options[:fork_worker] || fork_worker
|
256
|
-
@master_read.close
|
257
|
-
@suicide_pipe.close
|
258
|
-
@fork_writer.close
|
259
|
-
end
|
260
178
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
log "! Detected parent died, dying"
|
265
|
-
exit! 1
|
266
|
-
end
|
179
|
+
@master_read.close
|
180
|
+
@suicide_pipe.close
|
181
|
+
@fork_writer.close
|
267
182
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
|
273
|
-
elsif File.exist?("gems.rb")
|
274
|
-
log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
# Invoke any worker boot hooks so they can get
|
279
|
-
# things in shape before booting the app.
|
280
|
-
@launcher.config.run_hooks :before_worker_boot, index, @launcher.events
|
281
|
-
|
282
|
-
server = @server ||= start_server
|
283
|
-
restart_server = Queue.new << true << false
|
284
|
-
|
285
|
-
if fork_worker
|
286
|
-
restart_server.clear
|
287
|
-
Signal.trap "SIGCHLD" do
|
288
|
-
Process.wait(-1, Process::WNOHANG) rescue nil
|
289
|
-
wakeup!
|
290
|
-
end
|
291
|
-
|
292
|
-
Thread.new do
|
293
|
-
Puma.set_thread_name "worker fork pipe"
|
294
|
-
while (idx = @fork_pipe.gets)
|
295
|
-
idx = idx.to_i
|
296
|
-
if idx == -1 # stop server
|
297
|
-
if restart_server.length > 0
|
298
|
-
restart_server.clear
|
299
|
-
server.begin_restart(true)
|
300
|
-
@launcher.config.run_hooks :before_refork, nil, @launcher.events
|
301
|
-
nakayoshi_gc
|
302
|
-
end
|
303
|
-
elsif idx == 0 # restart server
|
304
|
-
restart_server << true << false
|
305
|
-
else # fork worker
|
306
|
-
pid = spawn_worker(idx, master)
|
307
|
-
@worker_write << "f#{pid}:#{idx}\n" rescue nil
|
308
|
-
end
|
309
|
-
end
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
Signal.trap "SIGTERM" do
|
314
|
-
@worker_write << "e#{Process.pid}\n" rescue nil
|
315
|
-
server.stop
|
316
|
-
restart_server << false
|
317
|
-
end
|
318
|
-
|
319
|
-
begin
|
320
|
-
@worker_write << "b#{Process.pid}:#{index}\n"
|
321
|
-
rescue SystemCallError, IOError
|
322
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
323
|
-
STDERR.puts "Master seems to have exited, exiting."
|
324
|
-
return
|
325
|
-
end
|
326
|
-
|
327
|
-
Thread.new(@worker_write) do |io|
|
328
|
-
Puma.set_thread_name "stat payload"
|
329
|
-
|
330
|
-
while true
|
331
|
-
sleep Const::WORKER_CHECK_INTERVAL
|
332
|
-
begin
|
333
|
-
io << "p#{Process.pid}#{server.stats.to_json}\n"
|
334
|
-
rescue IOError
|
335
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
336
|
-
break
|
337
|
-
end
|
338
|
-
end
|
183
|
+
pipes = { check_pipe: @check_pipe, worker_write: @worker_write }
|
184
|
+
if @options[:fork_worker]
|
185
|
+
pipes[:fork_pipe] = @fork_pipe
|
186
|
+
pipes[:wakeup] = @wakeup
|
339
187
|
end
|
340
188
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
ensure
|
347
|
-
@worker_write << "t#{Process.pid}\n" rescue nil
|
348
|
-
@worker_write.close
|
189
|
+
new_worker = Worker.new index: index,
|
190
|
+
master: master,
|
191
|
+
launcher: @launcher,
|
192
|
+
pipes: pipes
|
193
|
+
new_worker.run
|
349
194
|
end
|
350
195
|
|
351
196
|
def restart
|
@@ -387,6 +232,7 @@ module Puma
|
|
387
232
|
|
388
233
|
# Inside of a child process, this will return all zeroes, as @workers is only populated in
|
389
234
|
# the master process.
|
235
|
+
# @!attribute [r] stats
|
390
236
|
def stats
|
391
237
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
392
238
|
worker_status = @workers.map do |w|
|
@@ -415,6 +261,7 @@ module Puma
|
|
415
261
|
@options[:preload_app]
|
416
262
|
end
|
417
263
|
|
264
|
+
# @version 5.0.0
|
418
265
|
def fork_worker!
|
419
266
|
if (worker = @workers.find { |w| w.index == 0 })
|
420
267
|
worker.phase += 1
|
@@ -540,7 +387,7 @@ module Puma
|
|
540
387
|
@master_read, @worker_write = read, @wakeup
|
541
388
|
|
542
389
|
@launcher.config.run_hooks :before_fork, nil, @launcher.events
|
543
|
-
nakayoshi_gc
|
390
|
+
Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
|
544
391
|
|
545
392
|
spawn_workers
|
546
393
|
|
@@ -548,9 +395,9 @@ module Puma
|
|
548
395
|
stop
|
549
396
|
end
|
550
397
|
|
551
|
-
@launcher.events.fire_on_booted!
|
552
|
-
|
553
398
|
begin
|
399
|
+
booted = false
|
400
|
+
|
554
401
|
while @status == :run
|
555
402
|
begin
|
556
403
|
if @phased_restart
|
@@ -591,6 +438,10 @@ module Puma
|
|
591
438
|
when "p"
|
592
439
|
w.ping!(result.sub(/^\d+/,'').chomp)
|
593
440
|
@launcher.events.fire(:ping!, w)
|
441
|
+
if !booted && @workers.none? {|worker| worker.last_status.empty?}
|
442
|
+
@launcher.events.fire_on_booted!
|
443
|
+
booted = true
|
444
|
+
end
|
594
445
|
end
|
595
446
|
else
|
596
447
|
log "! Out-of-sync worker list, no #{pid} worker"
|
@@ -628,7 +479,9 @@ module Puma
|
|
628
479
|
rescue Errno::ECHILD
|
629
480
|
begin
|
630
481
|
Process.kill(0, w.pid)
|
631
|
-
|
482
|
+
# child still alive but has another parent (e.g., using fork_worker)
|
483
|
+
w.term if w.term?
|
484
|
+
false
|
632
485
|
rescue Errno::ESRCH, Errno::EPERM
|
633
486
|
true # child is already terminated
|
634
487
|
end
|
@@ -636,6 +489,7 @@ module Puma
|
|
636
489
|
end
|
637
490
|
end
|
638
491
|
|
492
|
+
# @version 5.0.0
|
639
493
|
def timeout_workers
|
640
494
|
@workers.each do |w|
|
641
495
|
if !w.term? && w.ping_timeout <= Time.now
|
@@ -644,16 +498,5 @@ module Puma
|
|
644
498
|
end
|
645
499
|
end
|
646
500
|
end
|
647
|
-
|
648
|
-
def nakayoshi_gc
|
649
|
-
return unless @options[:nakayoshi_fork]
|
650
|
-
log "! Promoting existing objects to old generation..."
|
651
|
-
4.times { GC.start(full_mark: false) }
|
652
|
-
if GC.respond_to?(:compact)
|
653
|
-
log "! Compacting..."
|
654
|
-
GC.compact
|
655
|
-
end
|
656
|
-
log "! Friendly fork preparation complete."
|
657
|
-
end
|
658
501
|
end
|
659
502
|
end
|