puma 4.3.6-java → 5.0.2-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 +1153 -518
- data/LICENSE +23 -20
- data/README.md +26 -13
- data/docs/architecture.md +3 -3
- data/docs/deployment.md +9 -3
- data/docs/fork_worker.md +31 -0
- data/docs/jungle/README.md +13 -0
- data/{tools → docs}/jungle/rc.d/README.md +0 -0
- data/{tools → docs}/jungle/rc.d/puma +0 -0
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/{tools → docs}/jungle/upstart/README.md +0 -0
- data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
- data/{tools → docs}/jungle/upstart/puma.conf +0 -0
- data/docs/signals.md +7 -6
- data/docs/systemd.md +1 -63
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/extconf.rb +4 -3
- data/ext/puma_http11/mini_ssl.c +15 -2
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
- data/ext/puma_http11/puma_http11.c +6 -38
- data/lib/puma.rb +20 -0
- data/lib/puma/app/status.rb +14 -1
- data/lib/puma/binder.rb +90 -68
- data/lib/puma/cli.rb +7 -15
- data/lib/puma/client.rb +62 -13
- data/lib/puma/cluster.rb +193 -74
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +31 -42
- data/lib/puma/const.rb +3 -3
- data/lib/puma/control_cli.rb +29 -17
- data/lib/puma/detect.rb +17 -0
- data/lib/puma/dsl.rb +144 -70
- data/lib/puma/error_logger.rb +97 -0
- data/lib/puma/events.rb +37 -31
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/launcher.rb +57 -31
- data/lib/puma/minissl.rb +68 -18
- data/lib/puma/minissl/context_builder.rb +0 -3
- data/lib/puma/null_io.rb +1 -1
- data/lib/puma/plugin.rb +1 -10
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/rack/builder.rb +0 -4
- data/lib/puma/reactor.rb +10 -16
- data/lib/puma/runner.rb +8 -36
- data/lib/puma/server.rb +161 -218
- data/lib/puma/single.rb +8 -64
- data/lib/puma/state_file.rb +6 -3
- data/lib/puma/thread_pool.rb +116 -51
- data/lib/puma/util.rb +1 -0
- data/lib/rack/handler/puma.rb +1 -3
- data/tools/{docker/Dockerfile → Dockerfile} +0 -0
- metadata +17 -19
- 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/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/lib/puma/cli.rb
CHANGED
@@ -80,7 +80,7 @@ module Puma
|
|
80
80
|
@launcher.run
|
81
81
|
end
|
82
82
|
|
83
|
-
|
83
|
+
private
|
84
84
|
def unsupported(str)
|
85
85
|
@events.error(str)
|
86
86
|
raise UnsupportedOption
|
@@ -112,21 +112,11 @@ module Puma
|
|
112
112
|
configure_control_url(arg)
|
113
113
|
end
|
114
114
|
|
115
|
-
# alias --control-url for backwards-compatibility
|
116
|
-
o.on "--control URL", "DEPRECATED alias for --control-url" do |arg|
|
117
|
-
configure_control_url(arg)
|
118
|
-
end
|
119
|
-
|
120
115
|
o.on "--control-token TOKEN",
|
121
116
|
"The token to use as authentication for the control server" do |arg|
|
122
117
|
@control_options[:auth_token] = arg
|
123
118
|
end
|
124
119
|
|
125
|
-
o.on "-d", "--daemon", "Daemonize the server into the background" do
|
126
|
-
user_config.daemonize
|
127
|
-
user_config.quiet
|
128
|
-
end
|
129
|
-
|
130
120
|
o.on "--debug", "Log lowlevel debugging information" do
|
131
121
|
user_config.debug
|
132
122
|
end
|
@@ -140,6 +130,12 @@ module Puma
|
|
140
130
|
user_config.environment arg
|
141
131
|
end
|
142
132
|
|
133
|
+
o.on "-f", "--fork-worker=[REQUESTS]", OptionParser::DecimalInteger,
|
134
|
+
"Fork new workers from existing worker. Cluster mode only",
|
135
|
+
"Auto-refork after REQUESTS (default 1000)" do |*args|
|
136
|
+
user_config.fork_worker(*args.compact)
|
137
|
+
end
|
138
|
+
|
143
139
|
o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
|
144
140
|
$LOAD_PATH.unshift(*arg.split(':'))
|
145
141
|
end
|
@@ -192,10 +188,6 @@ module Puma
|
|
192
188
|
end
|
193
189
|
end
|
194
190
|
|
195
|
-
o.on "--tcp-mode", "Run the app in raw TCP mode instead of HTTP mode" do
|
196
|
-
user_config.tcp_mode!
|
197
|
-
end
|
198
|
-
|
199
191
|
o.on "--early-hints", "Enable early hints support" do
|
200
192
|
user_config.early_hints
|
201
193
|
end
|
data/lib/puma/client.rb
CHANGED
@@ -85,6 +85,7 @@ module Puma
|
|
85
85
|
|
86
86
|
def_delegators :@io, :closed?
|
87
87
|
|
88
|
+
# @!attribute [r] inspect
|
88
89
|
def inspect
|
89
90
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
90
91
|
end
|
@@ -96,6 +97,7 @@ module Puma
|
|
96
97
|
env[HIJACK_IO] ||= @io
|
97
98
|
end
|
98
99
|
|
100
|
+
# @!attribute [r] in_data_phase
|
99
101
|
def in_data_phase
|
100
102
|
!@read_header
|
101
103
|
end
|
@@ -153,9 +155,11 @@ module Puma
|
|
153
155
|
|
154
156
|
begin
|
155
157
|
data = @io.read_nonblock(CHUNK_SIZE)
|
156
|
-
rescue
|
158
|
+
rescue IO::WaitReadable
|
157
159
|
return false
|
158
|
-
rescue
|
160
|
+
rescue EOFError
|
161
|
+
# Swallow error, don't log
|
162
|
+
rescue SystemCallError, IOError
|
159
163
|
raise ConnectionError, "Connection error detected during read"
|
160
164
|
end
|
161
165
|
|
@@ -238,12 +242,23 @@ module Puma
|
|
238
242
|
return false unless IO.select([@to_io], nil, nil, 0)
|
239
243
|
try_to_finish
|
240
244
|
end
|
245
|
+
|
246
|
+
# For documentation, see https://github.com/puma/puma/issues/1754
|
247
|
+
send(:alias_method, :jruby_eagerly_finish, :eagerly_finish)
|
241
248
|
end # IS_JRUBY
|
242
249
|
|
243
|
-
def finish
|
250
|
+
def finish(timeout)
|
244
251
|
return true if @ready
|
245
252
|
until try_to_finish
|
246
|
-
|
253
|
+
can_read = begin
|
254
|
+
IO.select([@to_io], nil, nil, timeout)
|
255
|
+
rescue ThreadPool::ForceShutdown
|
256
|
+
nil
|
257
|
+
end
|
258
|
+
unless can_read
|
259
|
+
write_error(408) if in_data_phase
|
260
|
+
raise ConnectionError
|
261
|
+
end
|
247
262
|
end
|
248
263
|
true
|
249
264
|
end
|
@@ -259,7 +274,7 @@ module Puma
|
|
259
274
|
return @peerip if @peerip
|
260
275
|
|
261
276
|
if @remote_addr_header
|
262
|
-
hdr = (@env[@remote_addr_header] ||
|
277
|
+
hdr = (@env[@remote_addr_header] || LOCALHOST_IP).split(/[\s,]/).first
|
263
278
|
@peerip = hdr
|
264
279
|
return hdr
|
265
280
|
end
|
@@ -267,6 +282,20 @@ module Puma
|
|
267
282
|
@peerip ||= @io.peeraddr.last
|
268
283
|
end
|
269
284
|
|
285
|
+
# Returns true if the persistent connection can be closed immediately
|
286
|
+
# without waiting for the configured idle/shutdown timeout.
|
287
|
+
# @version 5.0.0
|
288
|
+
#
|
289
|
+
def can_close?
|
290
|
+
# Allow connection to close if it's received at least one full request
|
291
|
+
# and hasn't received any data for a future request.
|
292
|
+
#
|
293
|
+
# From RFC 2616 section 8.1.4:
|
294
|
+
# Servers SHOULD always respond to at least one request per connection,
|
295
|
+
# if at all possible.
|
296
|
+
@requests_served > 0 && @parsed_bytes == 0
|
297
|
+
end
|
298
|
+
|
270
299
|
private
|
271
300
|
|
272
301
|
def setup_body
|
@@ -351,7 +380,7 @@ module Puma
|
|
351
380
|
|
352
381
|
begin
|
353
382
|
chunk = @io.read_nonblock(want)
|
354
|
-
rescue
|
383
|
+
rescue IO::WaitReadable
|
355
384
|
return false
|
356
385
|
rescue SystemCallError, IOError
|
357
386
|
raise ConnectionError, "Connection error detected during read"
|
@@ -397,7 +426,10 @@ module Puma
|
|
397
426
|
raise EOFError
|
398
427
|
end
|
399
428
|
|
400
|
-
|
429
|
+
if decode_chunk(chunk)
|
430
|
+
@env[CONTENT_LENGTH] = @chunked_content_length
|
431
|
+
return true
|
432
|
+
end
|
401
433
|
end
|
402
434
|
end
|
403
435
|
|
@@ -409,20 +441,37 @@ module Puma
|
|
409
441
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
410
442
|
@body.binmode
|
411
443
|
@tempfile = @body
|
444
|
+
@chunked_content_length = 0
|
412
445
|
|
413
|
-
|
446
|
+
if decode_chunk(body)
|
447
|
+
@env[CONTENT_LENGTH] = @chunked_content_length
|
448
|
+
return true
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
# @version 5.0.0
|
453
|
+
def write_chunk(str)
|
454
|
+
@chunked_content_length += @body.write(str)
|
414
455
|
end
|
415
456
|
|
416
457
|
def decode_chunk(chunk)
|
417
458
|
if @partial_part_left > 0
|
418
459
|
if @partial_part_left <= chunk.size
|
419
460
|
if @partial_part_left > 2
|
420
|
-
|
461
|
+
write_chunk(chunk[0..(@partial_part_left-3)]) # skip the \r\n
|
421
462
|
end
|
422
463
|
chunk = chunk[@partial_part_left..-1]
|
423
464
|
@partial_part_left = 0
|
424
465
|
else
|
425
|
-
|
466
|
+
if @partial_part_left > 2
|
467
|
+
if @partial_part_left == chunk.size + 1
|
468
|
+
# Don't include the last \r
|
469
|
+
write_chunk(chunk[0..(@partial_part_left-3)])
|
470
|
+
else
|
471
|
+
# don't include the last \r\n
|
472
|
+
write_chunk(chunk)
|
473
|
+
end
|
474
|
+
end
|
426
475
|
@partial_part_left -= chunk.size
|
427
476
|
return false
|
428
477
|
end
|
@@ -469,12 +518,12 @@ module Puma
|
|
469
518
|
|
470
519
|
case
|
471
520
|
when got == len
|
472
|
-
|
521
|
+
write_chunk(part[0..-3]) # to skip the ending \r\n
|
473
522
|
when got <= len - 2
|
474
|
-
|
523
|
+
write_chunk(part)
|
475
524
|
@partial_part_left = len - part.size
|
476
525
|
when got == len - 1 # edge where we get just \r but not \n
|
477
|
-
|
526
|
+
write_chunk(part[0..-2])
|
478
527
|
@partial_part_left = len - part.size
|
479
528
|
end
|
480
529
|
else
|
data/lib/puma/cluster.rb
CHANGED
@@ -24,9 +24,8 @@ module Puma
|
|
24
24
|
|
25
25
|
@phase = 0
|
26
26
|
@workers = []
|
27
|
-
@next_check =
|
27
|
+
@next_check = Time.now
|
28
28
|
|
29
|
-
@phased_state = :idle
|
30
29
|
@phased_restart = false
|
31
30
|
end
|
32
31
|
|
@@ -37,7 +36,7 @@ module Puma
|
|
37
36
|
begin
|
38
37
|
loop do
|
39
38
|
wait_workers
|
40
|
-
break if @workers.empty?
|
39
|
+
break if @workers.reject {|w| w.pid.nil?}.empty?
|
41
40
|
sleep 0.2
|
42
41
|
end
|
43
42
|
rescue Interrupt
|
@@ -73,12 +72,15 @@ module Puma
|
|
73
72
|
@first_term_sent = nil
|
74
73
|
@started_at = Time.now
|
75
74
|
@last_checkin = Time.now
|
76
|
-
@last_status =
|
75
|
+
@last_status = {}
|
77
76
|
@term = false
|
78
77
|
end
|
79
78
|
|
80
79
|
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
81
80
|
|
81
|
+
# @version 5.0.0
|
82
|
+
attr_writer :pid, :phase
|
83
|
+
|
82
84
|
def booted?
|
83
85
|
@stage == :booted
|
84
86
|
end
|
@@ -94,11 +96,18 @@ module Puma
|
|
94
96
|
|
95
97
|
def ping!(status)
|
96
98
|
@last_checkin = Time.now
|
97
|
-
|
99
|
+
require 'json'
|
100
|
+
@last_status = JSON.parse(status, symbolize_names: true)
|
98
101
|
end
|
99
102
|
|
100
|
-
|
101
|
-
|
103
|
+
# @see Puma::Cluster#check_workers
|
104
|
+
# @version 5.0.0
|
105
|
+
def ping_timeout
|
106
|
+
@last_checkin +
|
107
|
+
(booted? ?
|
108
|
+
@options[:worker_timeout] :
|
109
|
+
@options[:worker_boot_timeout]
|
110
|
+
)
|
102
111
|
end
|
103
112
|
|
104
113
|
def term
|
@@ -109,14 +118,14 @@ module Puma
|
|
109
118
|
@term ||= true
|
110
119
|
@first_term_sent ||= Time.now
|
111
120
|
end
|
112
|
-
Process.kill @signal, @pid
|
121
|
+
Process.kill @signal, @pid if @pid
|
113
122
|
rescue Errno::ESRCH
|
114
123
|
end
|
115
124
|
end
|
116
125
|
|
117
126
|
def kill
|
118
|
-
|
119
|
-
|
127
|
+
@signal = 'KILL'
|
128
|
+
term
|
120
129
|
end
|
121
130
|
|
122
131
|
def hup
|
@@ -130,27 +139,44 @@ module Puma
|
|
130
139
|
return if diff < 1
|
131
140
|
|
132
141
|
master = Process.pid
|
142
|
+
if @options[:fork_worker]
|
143
|
+
@fork_writer << "-1\n"
|
144
|
+
end
|
133
145
|
|
134
146
|
diff.times do
|
135
147
|
idx = next_worker_index
|
136
|
-
@launcher.config.run_hooks :before_worker_fork, idx
|
137
148
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
149
|
+
if @options[:fork_worker] && idx != 0
|
150
|
+
@fork_writer << "#{idx}\n"
|
151
|
+
pid = nil
|
152
|
+
else
|
153
|
+
pid = spawn_worker(idx, master)
|
143
154
|
end
|
144
155
|
|
145
156
|
debug "Spawned worker: #{pid}"
|
146
157
|
@workers << Worker.new(idx, pid, @phase, @options)
|
158
|
+
end
|
159
|
+
|
160
|
+
if @options[:fork_worker] &&
|
161
|
+
@workers.all? {|x| x.phase == @phase}
|
147
162
|
|
148
|
-
@
|
163
|
+
@fork_writer << "0\n"
|
149
164
|
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# @version 5.0.0
|
168
|
+
def spawn_worker(idx, master)
|
169
|
+
@launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
|
150
170
|
|
151
|
-
|
152
|
-
|
171
|
+
pid = fork { worker(idx, master) }
|
172
|
+
if !pid
|
173
|
+
log "! Complete inability to spawn new workers detected"
|
174
|
+
log "! Seppuku is the only choice."
|
175
|
+
exit! 1
|
153
176
|
end
|
177
|
+
|
178
|
+
@launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
|
179
|
+
pid
|
154
180
|
end
|
155
181
|
|
156
182
|
def cull_workers
|
@@ -168,6 +194,7 @@ module Puma
|
|
168
194
|
end
|
169
195
|
end
|
170
196
|
|
197
|
+
# @!attribute [r] next_worker_index
|
171
198
|
def next_worker_index
|
172
199
|
all_positions = 0...@options[:workers]
|
173
200
|
occupied_positions = @workers.map { |w| w.index }
|
@@ -179,26 +206,12 @@ module Puma
|
|
179
206
|
@workers.count { |w| !w.booted? } == 0
|
180
207
|
end
|
181
208
|
|
182
|
-
def check_workers
|
183
|
-
return if
|
209
|
+
def check_workers
|
210
|
+
return if @next_check >= Time.now
|
184
211
|
|
185
212
|
@next_check = Time.now + Const::WORKER_CHECK_INTERVAL
|
186
213
|
|
187
|
-
|
188
|
-
|
189
|
-
@workers.each do |w|
|
190
|
-
next if !w.booted? && !w.ping_timeout?(@options[:worker_boot_timeout])
|
191
|
-
if w.ping_timeout?(@options[:worker_timeout])
|
192
|
-
log "! Terminating timed out worker: #{w.pid}"
|
193
|
-
w.kill
|
194
|
-
any = true
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
# If we killed any timed out workers, try to catch them
|
199
|
-
# during this loop by giving the kernel time to kill them.
|
200
|
-
sleep 1 if any
|
201
|
-
|
214
|
+
timeout_workers
|
202
215
|
wait_workers
|
203
216
|
cull_workers
|
204
217
|
spawn_workers
|
@@ -211,17 +224,18 @@ module Puma
|
|
211
224
|
w = @workers.find { |x| x.phase != @phase }
|
212
225
|
|
213
226
|
if w
|
214
|
-
|
215
|
-
@phased_state = :waiting
|
216
|
-
log "- Stopping #{w.pid} for phased upgrade..."
|
217
|
-
end
|
218
|
-
|
227
|
+
log "- Stopping #{w.pid} for phased upgrade..."
|
219
228
|
unless w.term?
|
220
229
|
w.term
|
221
230
|
log "- #{w.signal} sent to #{w.pid}..."
|
222
231
|
end
|
223
232
|
end
|
224
233
|
end
|
234
|
+
|
235
|
+
@next_check = [
|
236
|
+
@workers.reject(&:term?).map(&:ping_timeout).min,
|
237
|
+
@next_check
|
238
|
+
].compact.min
|
225
239
|
end
|
226
240
|
|
227
241
|
def wakeup!
|
@@ -240,10 +254,16 @@ module Puma
|
|
240
254
|
$0 = title
|
241
255
|
|
242
256
|
Signal.trap "SIGINT", "IGNORE"
|
257
|
+
Signal.trap "SIGCHLD", "DEFAULT"
|
258
|
+
|
259
|
+
fork_worker = @options[:fork_worker] && index == 0
|
243
260
|
|
244
261
|
@workers = []
|
245
|
-
|
246
|
-
|
262
|
+
if !@options[:fork_worker] || fork_worker
|
263
|
+
@master_read.close
|
264
|
+
@suicide_pipe.close
|
265
|
+
@fork_writer.close
|
266
|
+
end
|
247
267
|
|
248
268
|
Thread.new do
|
249
269
|
Puma.set_thread_name "worker check pipe"
|
@@ -264,17 +284,49 @@ module Puma
|
|
264
284
|
|
265
285
|
# Invoke any worker boot hooks so they can get
|
266
286
|
# things in shape before booting the app.
|
267
|
-
@launcher.config.run_hooks :before_worker_boot, index
|
287
|
+
@launcher.config.run_hooks :before_worker_boot, index, @launcher.events
|
268
288
|
|
269
|
-
server = start_server
|
289
|
+
server = @server ||= start_server
|
290
|
+
restart_server = Queue.new << true << false
|
291
|
+
|
292
|
+
if fork_worker
|
293
|
+
restart_server.clear
|
294
|
+
worker_pids = []
|
295
|
+
Signal.trap "SIGCHLD" do
|
296
|
+
wakeup! if worker_pids.reject! do |p|
|
297
|
+
Process.wait(p, Process::WNOHANG) rescue true
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
Thread.new do
|
302
|
+
Puma.set_thread_name "worker fork pipe"
|
303
|
+
while (idx = @fork_pipe.gets)
|
304
|
+
idx = idx.to_i
|
305
|
+
if idx == -1 # stop server
|
306
|
+
if restart_server.length > 0
|
307
|
+
restart_server.clear
|
308
|
+
server.begin_restart(true)
|
309
|
+
@launcher.config.run_hooks :before_refork, nil, @launcher.events
|
310
|
+
nakayoshi_gc
|
311
|
+
end
|
312
|
+
elsif idx == 0 # restart server
|
313
|
+
restart_server << true << false
|
314
|
+
else # fork worker
|
315
|
+
worker_pids << pid = spawn_worker(idx, master)
|
316
|
+
@worker_write << "f#{pid}:#{idx}\n" rescue nil
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
270
321
|
|
271
322
|
Signal.trap "SIGTERM" do
|
272
323
|
@worker_write << "e#{Process.pid}\n" rescue nil
|
273
324
|
server.stop
|
325
|
+
restart_server << false
|
274
326
|
end
|
275
327
|
|
276
328
|
begin
|
277
|
-
@worker_write << "b#{Process.pid}\n"
|
329
|
+
@worker_write << "b#{Process.pid}:#{index}\n"
|
278
330
|
rescue SystemCallError, IOError
|
279
331
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
280
332
|
STDERR.puts "Master seems to have exited, exiting."
|
@@ -283,17 +335,12 @@ module Puma
|
|
283
335
|
|
284
336
|
Thread.new(@worker_write) do |io|
|
285
337
|
Puma.set_thread_name "stat payload"
|
286
|
-
base_payload = "p#{Process.pid}"
|
287
338
|
|
288
339
|
while true
|
289
340
|
sleep Const::WORKER_CHECK_INTERVAL
|
290
341
|
begin
|
291
|
-
|
292
|
-
|
293
|
-
t = server.pool_capacity || 0
|
294
|
-
m = server.max_threads || 0
|
295
|
-
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m} }\n!
|
296
|
-
io << payload
|
342
|
+
require 'json'
|
343
|
+
io << "p#{Process.pid}#{server.stats.to_json}\n"
|
297
344
|
rescue IOError
|
298
345
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
299
346
|
break
|
@@ -301,11 +348,11 @@ module Puma
|
|
301
348
|
end
|
302
349
|
end
|
303
350
|
|
304
|
-
server.run.join
|
351
|
+
server.run.join while restart_server.pop
|
305
352
|
|
306
353
|
# Invoke any worker shutdown hooks so they can prevent the worker
|
307
354
|
# exiting until any background operations are completed
|
308
|
-
@launcher.config.run_hooks :before_worker_shutdown, index
|
355
|
+
@launcher.config.run_hooks :before_worker_shutdown, index, @launcher.events
|
309
356
|
ensure
|
310
357
|
@worker_write << "t#{Process.pid}\n" rescue nil
|
311
358
|
@worker_write.close
|
@@ -350,20 +397,61 @@ module Puma
|
|
350
397
|
|
351
398
|
# Inside of a child process, this will return all zeroes, as @workers is only populated in
|
352
399
|
# the master process.
|
400
|
+
# @!attribute [r] stats
|
353
401
|
def stats
|
354
402
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
355
|
-
|
356
|
-
|
357
|
-
|
403
|
+
worker_status = @workers.map do |w|
|
404
|
+
{
|
405
|
+
started_at: w.started_at.utc.iso8601,
|
406
|
+
pid: w.pid,
|
407
|
+
index: w.index,
|
408
|
+
phase: w.phase,
|
409
|
+
booted: w.booted?,
|
410
|
+
last_checkin: w.last_checkin.utc.iso8601,
|
411
|
+
last_status: w.last_status,
|
412
|
+
}
|
413
|
+
end
|
414
|
+
|
415
|
+
{
|
416
|
+
started_at: @started_at.utc.iso8601,
|
417
|
+
workers: @workers.size,
|
418
|
+
phase: @phase,
|
419
|
+
booted_workers: worker_status.count { |w| w[:booted] },
|
420
|
+
old_workers: old_worker_count,
|
421
|
+
worker_status: worker_status,
|
422
|
+
}
|
358
423
|
end
|
359
424
|
|
360
425
|
def preload?
|
361
426
|
@options[:preload_app]
|
362
427
|
end
|
363
428
|
|
429
|
+
# @version 5.0.0
|
430
|
+
def fork_worker!
|
431
|
+
if (worker = @workers.find { |w| w.index == 0 })
|
432
|
+
worker.phase += 1
|
433
|
+
end
|
434
|
+
phased_restart
|
435
|
+
end
|
436
|
+
|
364
437
|
# We do this in a separate method to keep the lambda scope
|
365
438
|
# of the signals handlers as small as possible.
|
366
439
|
def setup_signals
|
440
|
+
if @options[:fork_worker]
|
441
|
+
Signal.trap "SIGURG" do
|
442
|
+
fork_worker!
|
443
|
+
end
|
444
|
+
|
445
|
+
# Auto-fork after the specified number of requests.
|
446
|
+
if (fork_requests = @options[:fork_worker].to_i) > 0
|
447
|
+
@launcher.events.register(:ping!) do |w|
|
448
|
+
fork_worker! if w.index == 0 &&
|
449
|
+
w.phase == 0 &&
|
450
|
+
w.last_status[:requests_count] >= fork_requests
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
367
455
|
Signal.trap "SIGCHLD" do
|
368
456
|
wakeup!
|
369
457
|
end
|
@@ -447,12 +535,11 @@ module Puma
|
|
447
535
|
#
|
448
536
|
@check_pipe, @suicide_pipe = Puma::Util.pipe
|
449
537
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
end
|
538
|
+
# Separate pipe used by worker 0 to receive commands to
|
539
|
+
# fork new worker processes.
|
540
|
+
@fork_pipe, @fork_writer = Puma::Util.pipe
|
541
|
+
|
542
|
+
log "Use Ctrl-C to stop"
|
456
543
|
|
457
544
|
redirect_io
|
458
545
|
|
@@ -464,7 +551,8 @@ module Puma
|
|
464
551
|
|
465
552
|
@master_read, @worker_write = read, @wakeup
|
466
553
|
|
467
|
-
@launcher.config.run_hooks :before_fork, nil
|
554
|
+
@launcher.config.run_hooks :before_fork, nil, @launcher.events
|
555
|
+
nakayoshi_gc
|
468
556
|
|
469
557
|
spawn_workers
|
470
558
|
|
@@ -475,8 +563,6 @@ module Puma
|
|
475
563
|
@launcher.events.fire_on_booted!
|
476
564
|
|
477
565
|
begin
|
478
|
-
force_check = false
|
479
|
-
|
480
566
|
while @status == :run
|
481
567
|
begin
|
482
568
|
if @phased_restart
|
@@ -484,34 +570,39 @@ module Puma
|
|
484
570
|
@phased_restart = false
|
485
571
|
end
|
486
572
|
|
487
|
-
check_workers
|
573
|
+
check_workers
|
488
574
|
|
489
|
-
|
490
|
-
|
491
|
-
res = IO.select([read], nil, nil, Const::WORKER_CHECK_INTERVAL)
|
575
|
+
res = IO.select([read], nil, nil, [0, @next_check - Time.now].max)
|
492
576
|
|
493
577
|
if res
|
494
578
|
req = read.read_nonblock(1)
|
495
579
|
|
580
|
+
@next_check = Time.now if req == "!"
|
496
581
|
next if !req || req == "!"
|
497
582
|
|
498
583
|
result = read.gets
|
499
584
|
pid = result.to_i
|
500
585
|
|
586
|
+
if req == "b" || req == "f"
|
587
|
+
pid, idx = result.split(':').map(&:to_i)
|
588
|
+
w = @workers.find {|x| x.index == idx}
|
589
|
+
w.pid = pid if w.pid.nil?
|
590
|
+
end
|
591
|
+
|
501
592
|
if w = @workers.find { |x| x.pid == pid }
|
502
593
|
case req
|
503
594
|
when "b"
|
504
595
|
w.boot!
|
505
596
|
log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
|
506
|
-
|
597
|
+
@next_check = Time.now
|
507
598
|
when "e"
|
508
599
|
# external term, see worker method, Signal.trap "SIGTERM"
|
509
600
|
w.instance_variable_set :@term, true
|
510
601
|
when "t"
|
511
602
|
w.term unless w.term?
|
512
|
-
force_check = true
|
513
603
|
when "p"
|
514
604
|
w.ping!(result.sub(/^\d+/,'').chomp)
|
605
|
+
@launcher.events.fire(:ping!, w)
|
515
606
|
end
|
516
607
|
else
|
517
608
|
log "! Out-of-sync worker list, no #{pid} worker"
|
@@ -538,6 +629,7 @@ module Puma
|
|
538
629
|
# `#term` if needed
|
539
630
|
def wait_workers
|
540
631
|
@workers.reject! do |w|
|
632
|
+
next false if w.pid.nil?
|
541
633
|
begin
|
542
634
|
if Process.wait(w.pid, Process::WNOHANG)
|
543
635
|
true
|
@@ -546,9 +638,36 @@ module Puma
|
|
546
638
|
nil
|
547
639
|
end
|
548
640
|
rescue Errno::ECHILD
|
549
|
-
|
641
|
+
begin
|
642
|
+
Process.kill(0, w.pid)
|
643
|
+
false # child still alive, but has another parent
|
644
|
+
rescue Errno::ESRCH, Errno::EPERM
|
645
|
+
true # child is already terminated
|
646
|
+
end
|
550
647
|
end
|
551
648
|
end
|
552
649
|
end
|
650
|
+
|
651
|
+
# @version 5.0.0
|
652
|
+
def timeout_workers
|
653
|
+
@workers.each do |w|
|
654
|
+
if !w.term? && w.ping_timeout <= Time.now
|
655
|
+
log "! Terminating timed out worker: #{w.pid}"
|
656
|
+
w.kill
|
657
|
+
end
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
# @version 5.0.0
|
662
|
+
def nakayoshi_gc
|
663
|
+
return unless @options[:nakayoshi_fork]
|
664
|
+
log "! Promoting existing objects to old generation..."
|
665
|
+
4.times { GC.start(full_mark: false) }
|
666
|
+
if GC.respond_to?(:compact)
|
667
|
+
log "! Compacting..."
|
668
|
+
GC.compact
|
669
|
+
end
|
670
|
+
log "! Friendly fork preparation complete."
|
671
|
+
end
|
553
672
|
end
|
554
673
|
end
|