puma-simon 3.7.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 +7 -0
- data/.github/issue_template.md +20 -0
- data/.gitignore +18 -0
- data/.hoeignore +12 -0
- data/.travis.yml +29 -0
- data/DEPLOYMENT.md +91 -0
- data/Gemfile +12 -0
- data/History.md +1254 -0
- data/LICENSE +26 -0
- data/Manifest.txt +78 -0
- data/README.md +353 -0
- data/Rakefile +158 -0
- data/Release.md +9 -0
- data/bin/puma +10 -0
- data/bin/puma-wild +31 -0
- data/bin/pumactl +12 -0
- data/docs/nginx.md +80 -0
- data/docs/signals.md +43 -0
- data/docs/systemd.md +197 -0
- data/examples/CA/cacert.pem +23 -0
- data/examples/CA/newcerts/cert_1.pem +19 -0
- data/examples/CA/newcerts/cert_2.pem +19 -0
- data/examples/CA/private/cakeypair.pem +30 -0
- data/examples/CA/serial +1 -0
- data/examples/config.rb +200 -0
- data/examples/plugins/redis_stop_puma.rb +46 -0
- data/examples/puma/cert_puma.pem +19 -0
- data/examples/puma/client-certs/ca.crt +19 -0
- data/examples/puma/client-certs/ca.key +27 -0
- data/examples/puma/client-certs/client.crt +19 -0
- data/examples/puma/client-certs/client.key +27 -0
- data/examples/puma/client-certs/client_expired.crt +19 -0
- data/examples/puma/client-certs/client_expired.key +27 -0
- data/examples/puma/client-certs/client_unknown.crt +19 -0
- data/examples/puma/client-certs/client_unknown.key +27 -0
- data/examples/puma/client-certs/generate.rb +78 -0
- data/examples/puma/client-certs/keystore.jks +0 -0
- data/examples/puma/client-certs/server.crt +19 -0
- data/examples/puma/client-certs/server.key +27 -0
- data/examples/puma/client-certs/server.p12 +0 -0
- data/examples/puma/client-certs/unknown_ca.crt +19 -0
- data/examples/puma/client-certs/unknown_ca.key +27 -0
- data/examples/puma/csr_puma.pem +11 -0
- data/examples/puma/keystore.jks +0 -0
- data/examples/puma/puma_keypair.pem +15 -0
- data/examples/qc_config.rb +13 -0
- data/ext/puma_http11/PumaHttp11Service.java +17 -0
- data/ext/puma_http11/ext_help.h +15 -0
- data/ext/puma_http11/extconf.rb +15 -0
- data/ext/puma_http11/http11_parser.c +1069 -0
- data/ext/puma_http11/http11_parser.h +65 -0
- data/ext/puma_http11/http11_parser.java.rl +161 -0
- data/ext/puma_http11/http11_parser.rl +147 -0
- data/ext/puma_http11/http11_parser_common.rl +54 -0
- data/ext/puma_http11/io_buffer.c +155 -0
- data/ext/puma_http11/mini_ssl.c +457 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +234 -0
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +473 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +339 -0
- data/ext/puma_http11/puma_http11.c +500 -0
- data/gemfiles/2.1-Gemfile +12 -0
- data/lib/puma.rb +15 -0
- data/lib/puma/accept_nonblock.rb +23 -0
- data/lib/puma/app/status.rb +66 -0
- data/lib/puma/binder.rb +402 -0
- data/lib/puma/cli.rb +220 -0
- data/lib/puma/client.rb +434 -0
- data/lib/puma/cluster.rb +510 -0
- data/lib/puma/commonlogger.rb +106 -0
- data/lib/puma/compat.rb +14 -0
- data/lib/puma/configuration.rb +364 -0
- data/lib/puma/const.rb +224 -0
- data/lib/puma/control_cli.rb +259 -0
- data/lib/puma/convenient.rb +23 -0
- data/lib/puma/daemon_ext.rb +31 -0
- data/lib/puma/delegation.rb +11 -0
- data/lib/puma/detect.rb +13 -0
- data/lib/puma/dsl.rb +486 -0
- data/lib/puma/events.rb +152 -0
- data/lib/puma/io_buffer.rb +7 -0
- data/lib/puma/java_io_buffer.rb +45 -0
- data/lib/puma/jruby_restart.rb +83 -0
- data/lib/puma/launcher.rb +410 -0
- data/lib/puma/minissl.rb +221 -0
- data/lib/puma/null_io.rb +42 -0
- data/lib/puma/plugin.rb +115 -0
- data/lib/puma/plugin/tmp_restart.rb +35 -0
- data/lib/puma/rack/backports/uri/common_193.rb +33 -0
- data/lib/puma/rack/builder.rb +298 -0
- data/lib/puma/rack/urlmap.rb +91 -0
- data/lib/puma/rack_default.rb +7 -0
- data/lib/puma/reactor.rb +210 -0
- data/lib/puma/runner.rb +171 -0
- data/lib/puma/server.rb +949 -0
- data/lib/puma/single.rb +112 -0
- data/lib/puma/state_file.rb +29 -0
- data/lib/puma/tcp_logger.rb +39 -0
- data/lib/puma/thread_pool.rb +297 -0
- data/lib/puma/util.rb +128 -0
- data/lib/rack/handler/puma.rb +78 -0
- data/puma.gemspec +52 -0
- data/test/ab_rs.rb +22 -0
- data/test/config.rb +2 -0
- data/test/config/app.rb +9 -0
- data/test/config/plugin.rb +1 -0
- data/test/config/settings.rb +2 -0
- data/test/config/state_file_testing_config.rb +14 -0
- data/test/hello-bind.ru +2 -0
- data/test/hello-delay.ru +3 -0
- data/test/hello-map.ru +3 -0
- data/test/hello-post.ru +4 -0
- data/test/hello-stuck.ru +1 -0
- data/test/hello-tcp.ru +5 -0
- data/test/hello.ru +1 -0
- data/test/hijack.ru +6 -0
- data/test/hijack2.ru +5 -0
- data/test/lobster.ru +4 -0
- data/test/shell/run.sh +24 -0
- data/test/shell/t1.rb +19 -0
- data/test/shell/t1_conf.rb +3 -0
- data/test/shell/t2.rb +17 -0
- data/test/shell/t2_conf.rb +6 -0
- data/test/shell/t3.rb +25 -0
- data/test/shell/t3_conf.rb +5 -0
- data/test/slow.ru +4 -0
- data/test/ssl_config.rb +4 -0
- data/test/test_app_status.rb +93 -0
- data/test/test_binder.rb +31 -0
- data/test/test_cli.rb +209 -0
- data/test/test_config.rb +95 -0
- data/test/test_events.rb +161 -0
- data/test/test_helper.rb +50 -0
- data/test/test_http10.rb +27 -0
- data/test/test_http11.rb +186 -0
- data/test/test_integration.rb +247 -0
- data/test/test_iobuffer.rb +39 -0
- data/test/test_minissl.rb +29 -0
- data/test/test_null_io.rb +49 -0
- data/test/test_persistent.rb +245 -0
- data/test/test_puma_server.rb +626 -0
- data/test/test_puma_server_ssl.rb +222 -0
- data/test/test_rack_handler.rb +57 -0
- data/test/test_rack_server.rb +138 -0
- data/test/test_tcp_logger.rb +39 -0
- data/test/test_tcp_rack.rb +36 -0
- data/test/test_thread_pool.rb +250 -0
- data/test/test_unix_socket.rb +35 -0
- data/test/test_web_server.rb +88 -0
- data/tools/jungle/README.md +9 -0
- data/tools/jungle/init.d/README.md +59 -0
- data/tools/jungle/init.d/puma +421 -0
- data/tools/jungle/init.d/run-puma +18 -0
- data/tools/jungle/upstart/README.md +61 -0
- data/tools/jungle/upstart/puma-manager.conf +31 -0
- data/tools/jungle/upstart/puma.conf +69 -0
- data/tools/trickletest.rb +45 -0
- metadata +297 -0
data/lib/puma/cluster.rb
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
require 'puma/runner'
|
|
2
|
+
require 'puma/util'
|
|
3
|
+
require 'puma/plugin'
|
|
4
|
+
|
|
5
|
+
require 'time'
|
|
6
|
+
|
|
7
|
+
module Puma
|
|
8
|
+
class Cluster < Runner
|
|
9
|
+
WORKER_CHECK_INTERVAL = 5
|
|
10
|
+
|
|
11
|
+
def initialize(cli, events)
|
|
12
|
+
super cli, events
|
|
13
|
+
|
|
14
|
+
@phase = 0
|
|
15
|
+
@workers = []
|
|
16
|
+
@next_check = nil
|
|
17
|
+
|
|
18
|
+
@phased_state = :idle
|
|
19
|
+
@phased_restart = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def stop_workers
|
|
23
|
+
log "- Gracefully shutting down workers..."
|
|
24
|
+
@workers.each { |x| x.term }
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
Process.waitall
|
|
28
|
+
rescue Interrupt
|
|
29
|
+
log "! Cancelled waiting for workers"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def start_phased_restart
|
|
34
|
+
@phase += 1
|
|
35
|
+
log "- Starting phased worker restart, phase: #{@phase}"
|
|
36
|
+
|
|
37
|
+
# Be sure to change the directory again before loading
|
|
38
|
+
# the app. This way we can pick up new code.
|
|
39
|
+
dir = @launcher.restart_dir
|
|
40
|
+
log "+ Changing to #{dir}"
|
|
41
|
+
Dir.chdir dir
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def redirect_io
|
|
45
|
+
super
|
|
46
|
+
|
|
47
|
+
@workers.each { |x| x.hup }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class Worker
|
|
51
|
+
def initialize(idx, pid, phase, options)
|
|
52
|
+
@index = idx
|
|
53
|
+
@pid = pid
|
|
54
|
+
@phase = phase
|
|
55
|
+
@stage = :started
|
|
56
|
+
@signal = "TERM"
|
|
57
|
+
@options = options
|
|
58
|
+
@first_term_sent = nil
|
|
59
|
+
@last_checkin = Time.now
|
|
60
|
+
@last_status = '{}'
|
|
61
|
+
@dead = false
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status
|
|
65
|
+
|
|
66
|
+
def booted?
|
|
67
|
+
@stage == :booted
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def boot!
|
|
71
|
+
@last_checkin = Time.now
|
|
72
|
+
@stage = :booted
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def dead?
|
|
76
|
+
@dead
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def dead!
|
|
80
|
+
@dead = true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def ping!(status)
|
|
84
|
+
@last_checkin = Time.now
|
|
85
|
+
@last_status = status
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def ping_timeout?(which)
|
|
89
|
+
Time.now - @last_checkin > which
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def term
|
|
93
|
+
begin
|
|
94
|
+
if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
|
|
95
|
+
@signal = "KILL"
|
|
96
|
+
else
|
|
97
|
+
@first_term_sent ||= Time.now
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
Process.kill @signal, @pid
|
|
101
|
+
rescue Errno::ESRCH
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def kill
|
|
106
|
+
Process.kill "KILL", @pid
|
|
107
|
+
rescue Errno::ESRCH
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def hup
|
|
111
|
+
Process.kill "HUP", @pid
|
|
112
|
+
rescue Errno::ESRCH
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def spawn_workers
|
|
117
|
+
diff = @options[:workers] - @workers.size
|
|
118
|
+
return if diff < 1
|
|
119
|
+
|
|
120
|
+
master = Process.pid
|
|
121
|
+
|
|
122
|
+
diff.times do
|
|
123
|
+
idx = next_worker_index
|
|
124
|
+
@launcher.config.run_hooks :before_worker_fork, idx
|
|
125
|
+
|
|
126
|
+
pid = fork { worker(idx, master) }
|
|
127
|
+
if !pid
|
|
128
|
+
log "! Complete inability to spawn new workers detected"
|
|
129
|
+
log "! Seppuku is the only choice."
|
|
130
|
+
exit! 1
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
debug "Spawned worker: #{pid}"
|
|
134
|
+
@workers << Worker.new(idx, pid, @phase, @options)
|
|
135
|
+
|
|
136
|
+
@launcher.config.run_hooks :after_worker_fork, idx
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
if diff > 0
|
|
140
|
+
@phased_state = :idle
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def cull_workers
|
|
145
|
+
diff = @workers.size - @options[:workers]
|
|
146
|
+
return if diff < 1
|
|
147
|
+
|
|
148
|
+
debug "Culling #{diff.inspect} workers"
|
|
149
|
+
|
|
150
|
+
workers_to_cull = @workers[-diff,diff]
|
|
151
|
+
debug "Workers to cull: #{workers_to_cull.inspect}"
|
|
152
|
+
|
|
153
|
+
workers_to_cull.each do |worker|
|
|
154
|
+
log "- Worker #{worker.index} (pid: #{worker.pid}) terminating"
|
|
155
|
+
worker.term
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def next_worker_index
|
|
160
|
+
all_positions = 0...@options[:workers]
|
|
161
|
+
occupied_positions = @workers.map { |w| w.index }
|
|
162
|
+
available_positions = all_positions.to_a - occupied_positions
|
|
163
|
+
available_positions.first
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def all_workers_booted?
|
|
167
|
+
@workers.count { |w| !w.booted? } == 0
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def check_workers(force=false)
|
|
171
|
+
return if !force && @next_check && @next_check >= Time.now
|
|
172
|
+
|
|
173
|
+
@next_check = Time.now + WORKER_CHECK_INTERVAL
|
|
174
|
+
|
|
175
|
+
any = false
|
|
176
|
+
|
|
177
|
+
@workers.each do |w|
|
|
178
|
+
next if !w.booted? && !w.ping_timeout?(@options[:worker_boot_timeout])
|
|
179
|
+
if w.ping_timeout?(@options[:worker_timeout])
|
|
180
|
+
log "! Terminating timed out worker: #{w.pid}"
|
|
181
|
+
w.kill
|
|
182
|
+
any = true
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# If we killed any timed out workers, try to catch them
|
|
187
|
+
# during this loop by giving the kernel time to kill them.
|
|
188
|
+
sleep 1 if any
|
|
189
|
+
|
|
190
|
+
while @workers.any?
|
|
191
|
+
pid = Process.waitpid(-1, Process::WNOHANG)
|
|
192
|
+
break unless pid
|
|
193
|
+
|
|
194
|
+
@workers.delete_if { |w| w.pid == pid }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
@workers.delete_if(&:dead?)
|
|
198
|
+
|
|
199
|
+
cull_workers
|
|
200
|
+
spawn_workers
|
|
201
|
+
|
|
202
|
+
if all_workers_booted?
|
|
203
|
+
# If we're running at proper capacity, check to see if
|
|
204
|
+
# we need to phase any workers out (which will restart
|
|
205
|
+
# in the right phase).
|
|
206
|
+
#
|
|
207
|
+
w = @workers.find { |x| x.phase != @phase }
|
|
208
|
+
|
|
209
|
+
if w
|
|
210
|
+
if @phased_state == :idle
|
|
211
|
+
@phased_state = :waiting
|
|
212
|
+
log "- Stopping #{w.pid} for phased upgrade..."
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
w.term
|
|
216
|
+
log "- #{w.signal} sent to #{w.pid}..."
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def wakeup!
|
|
222
|
+
return unless @wakeup
|
|
223
|
+
|
|
224
|
+
begin
|
|
225
|
+
@wakeup.write "!" unless @wakeup.closed?
|
|
226
|
+
rescue SystemCallError, IOError
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def worker(index, master)
|
|
231
|
+
title = "puma: cluster worker #{index}: #{master}"
|
|
232
|
+
title << " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
|
233
|
+
$0 = title
|
|
234
|
+
|
|
235
|
+
Signal.trap "SIGINT", "IGNORE"
|
|
236
|
+
|
|
237
|
+
@workers = []
|
|
238
|
+
@master_read.close
|
|
239
|
+
@suicide_pipe.close
|
|
240
|
+
|
|
241
|
+
Thread.new do
|
|
242
|
+
IO.select [@check_pipe]
|
|
243
|
+
log "! Detected parent died, dying"
|
|
244
|
+
exit! 1
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# If we're not running under a Bundler context, then
|
|
248
|
+
# report the info about the context we will be using
|
|
249
|
+
if !ENV['BUNDLE_GEMFILE']
|
|
250
|
+
if File.exist?("Gemfile")
|
|
251
|
+
log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
|
|
252
|
+
elsif File.exist?("gems.rb")
|
|
253
|
+
log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Invoke any worker boot hooks so they can get
|
|
258
|
+
# things in shape before booting the app.
|
|
259
|
+
@launcher.config.run_hooks :before_worker_boot, index
|
|
260
|
+
|
|
261
|
+
server = start_server
|
|
262
|
+
|
|
263
|
+
Signal.trap "SIGTERM" do
|
|
264
|
+
server.stop
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
begin
|
|
268
|
+
@worker_write << "b#{Process.pid}\n"
|
|
269
|
+
rescue SystemCallError, IOError
|
|
270
|
+
STDERR.puts "Master seems to have exited, exiting."
|
|
271
|
+
return
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
Thread.new(@worker_write) do |io|
|
|
275
|
+
base_payload = "p#{Process.pid}"
|
|
276
|
+
|
|
277
|
+
while true
|
|
278
|
+
sleep WORKER_CHECK_INTERVAL
|
|
279
|
+
begin
|
|
280
|
+
b = server.backlog
|
|
281
|
+
r = server.running
|
|
282
|
+
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r} }\n!
|
|
283
|
+
io << payload
|
|
284
|
+
rescue IOError
|
|
285
|
+
break
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
server.run.join
|
|
291
|
+
|
|
292
|
+
# Invoke any worker shutdown hooks so they can prevent the worker
|
|
293
|
+
# exiting until any background operations are completed
|
|
294
|
+
@launcher.config.run_hooks :before_worker_shutdown, index
|
|
295
|
+
ensure
|
|
296
|
+
@worker_write << "t#{Process.pid}\n" rescue nil
|
|
297
|
+
@worker_write.close
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def restart
|
|
301
|
+
@restart = true
|
|
302
|
+
stop
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def phased_restart
|
|
306
|
+
return false if @options[:preload_app]
|
|
307
|
+
|
|
308
|
+
@phased_restart = true
|
|
309
|
+
wakeup!
|
|
310
|
+
|
|
311
|
+
true
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def stop
|
|
315
|
+
@status = :stop
|
|
316
|
+
wakeup!
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def stop_blocked
|
|
320
|
+
@status = :stop if @status == :run
|
|
321
|
+
wakeup!
|
|
322
|
+
@control.stop(true) if @control
|
|
323
|
+
Process.waitall
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def halt
|
|
327
|
+
@status = :halt
|
|
328
|
+
wakeup!
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def reload_worker_directory
|
|
332
|
+
dir = @launcher.restart_dir
|
|
333
|
+
log "+ Changing to #{dir}"
|
|
334
|
+
Dir.chdir dir
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def stats
|
|
338
|
+
old_worker_count = @workers.count { |w| w.phase != @phase }
|
|
339
|
+
booted_worker_count = @workers.count { |w| w.booted? }
|
|
340
|
+
worker_status = '[' + @workers.map{ |w| %Q!{ "pid": #{w.pid}, "index": #{w.index}, "phase": #{w.phase}, "booted": #{w.booted?}, "last_checkin": "#{w.last_checkin.utc.iso8601}", "last_status": #{w.last_status} }!}.join(",") + ']'
|
|
341
|
+
%Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }!
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def preload?
|
|
345
|
+
@options[:preload_app]
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# We do this in a separate method to keep the lambda scope
|
|
349
|
+
# of the signals handlers as small as possible.
|
|
350
|
+
def setup_signals
|
|
351
|
+
Signal.trap "SIGCHLD" do
|
|
352
|
+
wakeup!
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
Signal.trap "TTIN" do
|
|
356
|
+
@options[:workers] += 1
|
|
357
|
+
wakeup!
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
Signal.trap "TTOU" do
|
|
361
|
+
@options[:workers] -= 1 if @options[:workers] >= 2
|
|
362
|
+
wakeup!
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
master_pid = Process.pid
|
|
366
|
+
|
|
367
|
+
Signal.trap "SIGTERM" do
|
|
368
|
+
# The worker installs their own SIGTERM when booted.
|
|
369
|
+
# Until then, this is run by the worker and the worker
|
|
370
|
+
# should just exit if they get it.
|
|
371
|
+
if Process.pid != master_pid
|
|
372
|
+
log "Early termination of worker"
|
|
373
|
+
exit! 0
|
|
374
|
+
else
|
|
375
|
+
stop
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def run
|
|
381
|
+
@status = :run
|
|
382
|
+
|
|
383
|
+
output_header "cluster"
|
|
384
|
+
|
|
385
|
+
log "* Process workers: #{@options[:workers]}"
|
|
386
|
+
|
|
387
|
+
before = Thread.list
|
|
388
|
+
|
|
389
|
+
if preload?
|
|
390
|
+
log "* Preloading application"
|
|
391
|
+
load_and_bind
|
|
392
|
+
|
|
393
|
+
after = Thread.list
|
|
394
|
+
|
|
395
|
+
if after.size > before.size
|
|
396
|
+
threads = (after - before)
|
|
397
|
+
if threads.first.respond_to? :backtrace
|
|
398
|
+
log "! WARNING: Detected #{after.size-before.size} Thread(s) started in app boot:"
|
|
399
|
+
threads.each do |t|
|
|
400
|
+
log "! #{t.inspect} - #{t.backtrace ? t.backtrace.first : ''}"
|
|
401
|
+
end
|
|
402
|
+
else
|
|
403
|
+
log "! WARNING: Detected #{after.size-before.size} Thread(s) started in app boot"
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
else
|
|
407
|
+
log "* Phased restart available"
|
|
408
|
+
|
|
409
|
+
unless @launcher.config.app_configured?
|
|
410
|
+
error "No application configured, nothing to run"
|
|
411
|
+
exit 1
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
@launcher.binder.parse @options[:binds], self
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
read, @wakeup = Puma::Util.pipe
|
|
418
|
+
|
|
419
|
+
setup_signals
|
|
420
|
+
|
|
421
|
+
# Used by the workers to detect if the master process dies.
|
|
422
|
+
# If select says that @check_pipe is ready, it's because the
|
|
423
|
+
# master has exited and @suicide_pipe has been automatically
|
|
424
|
+
# closed.
|
|
425
|
+
#
|
|
426
|
+
@check_pipe, @suicide_pipe = Puma::Util.pipe
|
|
427
|
+
|
|
428
|
+
if daemon?
|
|
429
|
+
log "* Daemonizing..."
|
|
430
|
+
Process.daemon(true)
|
|
431
|
+
else
|
|
432
|
+
log "Use Ctrl-C to stop"
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
redirect_io
|
|
436
|
+
|
|
437
|
+
Plugins.fire_background
|
|
438
|
+
|
|
439
|
+
@launcher.write_state
|
|
440
|
+
|
|
441
|
+
start_control
|
|
442
|
+
|
|
443
|
+
@master_read, @worker_write = read, @wakeup
|
|
444
|
+
|
|
445
|
+
@launcher.config.run_hooks :before_fork, nil
|
|
446
|
+
|
|
447
|
+
spawn_workers
|
|
448
|
+
|
|
449
|
+
Signal.trap "SIGINT" do
|
|
450
|
+
stop
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
@launcher.events.fire_on_booted!
|
|
454
|
+
|
|
455
|
+
begin
|
|
456
|
+
force_check = false
|
|
457
|
+
|
|
458
|
+
while @status == :run
|
|
459
|
+
begin
|
|
460
|
+
if @phased_restart
|
|
461
|
+
start_phased_restart
|
|
462
|
+
@phased_restart = false
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
check_workers force_check
|
|
466
|
+
|
|
467
|
+
force_check = false
|
|
468
|
+
|
|
469
|
+
res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
|
|
470
|
+
|
|
471
|
+
if res
|
|
472
|
+
req = read.read_nonblock(1)
|
|
473
|
+
|
|
474
|
+
next if !req || req == "!"
|
|
475
|
+
|
|
476
|
+
result = read.gets
|
|
477
|
+
pid = result.to_i
|
|
478
|
+
|
|
479
|
+
if w = @workers.find { |x| x.pid == pid }
|
|
480
|
+
case req
|
|
481
|
+
when "b"
|
|
482
|
+
w.boot!
|
|
483
|
+
log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
|
|
484
|
+
force_check = true
|
|
485
|
+
when "t"
|
|
486
|
+
w.dead!
|
|
487
|
+
force_check = true
|
|
488
|
+
when "p"
|
|
489
|
+
w.ping!(result.sub(/^\d+/,'').chomp)
|
|
490
|
+
end
|
|
491
|
+
else
|
|
492
|
+
log "! Out-of-sync worker list, no #{pid} worker"
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
rescue Interrupt
|
|
497
|
+
@status = :stop
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
stop_workers unless @status == :halt
|
|
502
|
+
ensure
|
|
503
|
+
@check_pipe.close
|
|
504
|
+
@suicide_pipe.close
|
|
505
|
+
read.close
|
|
506
|
+
@wakeup.close
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
end
|