puma 3.12.1 → 5.6.7
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 +1608 -447
- data/LICENSE +23 -20
- data/README.md +175 -63
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +59 -21
- data/docs/compile_options.md +21 -0
- data/docs/deployment.md +69 -58
- data/docs/fork_worker.md +33 -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/docs/kubernetes.md +66 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +22 -12
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +47 -22
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +95 -120
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +57 -2
- data/ext/puma_http11/http11_parser.c +105 -117
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +22 -38
- data/ext/puma_http11/http11_parser.rl +4 -2
- data/ext/puma_http11/http11_parser_common.rl +4 -4
- data/ext/puma_http11/mini_ssl.c +339 -98
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +124 -71
- data/ext/puma_http11/puma_http11.c +35 -51
- data/lib/puma/app/status.rb +71 -49
- data/lib/puma/binder.rb +234 -137
- data/lib/puma/cli.rb +28 -18
- data/lib/puma/client.rb +350 -230
- data/lib/puma/cluster/worker.rb +173 -0
- data/lib/puma/cluster/worker_handle.rb +94 -0
- data/lib/puma/cluster.rb +247 -232
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +61 -51
- data/lib/puma/const.rb +42 -21
- data/lib/puma/control_cli.rb +115 -67
- data/lib/puma/detect.rb +29 -2
- data/lib/puma/dsl.rb +619 -123
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +55 -31
- data/lib/puma/io_buffer.rb +7 -5
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher.rb +193 -69
- data/lib/puma/minissl/context_builder.rb +81 -0
- data/lib/puma/minissl.rb +170 -65
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/plugin.rb +7 -13
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +3 -5
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +85 -316
- data/lib/puma/request.rb +476 -0
- data/lib/puma/runner.rb +48 -55
- data/lib/puma/server.rb +305 -695
- data/lib/puma/single.rb +11 -67
- data/lib/puma/state_file.rb +48 -8
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +132 -82
- data/lib/puma/util.rb +33 -10
- data/lib/puma.rb +56 -0
- data/lib/rack/handler/puma.rb +5 -6
- data/lib/rack/version_restriction.rb +15 -0
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +46 -29
- data/ext/puma_http11/io_buffer.c +0 -155
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -25
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
- data/lib/puma/java_io_buffer.rb +0 -47
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- 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/{tools → docs}/jungle/rc.d/puma.conf +0 -0
data/lib/puma/cluster.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
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
10
|
|
@@ -11,24 +13,17 @@ module Puma
|
|
11
13
|
# to boot and serve a Ruby application when puma "workers" are needed
|
12
14
|
# i.e. when using multi-processes. For example `$ puma -w 5`
|
13
15
|
#
|
14
|
-
# At the core of this class is running an instance of `Puma::Server` which
|
15
|
-
# gets created via the `start_server` method from the `Puma::Runner` class
|
16
|
-
# that this inherits from.
|
17
|
-
#
|
18
16
|
# An instance of this class will spawn the number of processes passed in
|
19
17
|
# via the `spawn_workers` method call. Each worker will have it's own
|
20
18
|
# instance of a `Puma::Server`.
|
21
19
|
class Cluster < Runner
|
22
|
-
WORKER_CHECK_INTERVAL = 5
|
23
|
-
|
24
20
|
def initialize(cli, events)
|
25
21
|
super cli, events
|
26
22
|
|
27
23
|
@phase = 0
|
28
24
|
@workers = []
|
29
|
-
@next_check =
|
25
|
+
@next_check = Time.now
|
30
26
|
|
31
|
-
@phased_state = :idle
|
32
27
|
@phased_restart = false
|
33
28
|
end
|
34
29
|
|
@@ -37,13 +32,18 @@ module Puma
|
|
37
32
|
@workers.each { |x| x.term }
|
38
33
|
|
39
34
|
begin
|
40
|
-
|
35
|
+
loop do
|
36
|
+
wait_workers
|
37
|
+
break if @workers.reject {|w| w.pid.nil?}.empty?
|
38
|
+
sleep 0.2
|
39
|
+
end
|
41
40
|
rescue Interrupt
|
42
41
|
log "! Cancelled waiting for workers"
|
43
42
|
end
|
44
43
|
end
|
45
44
|
|
46
45
|
def start_phased_restart
|
46
|
+
@events.fire_on_restart!
|
47
47
|
@phase += 1
|
48
48
|
log "- Starting phased worker restart, phase: #{@phase}"
|
49
49
|
|
@@ -60,155 +60,103 @@ module Puma
|
|
60
60
|
@workers.each { |x| x.hup }
|
61
61
|
end
|
62
62
|
|
63
|
-
class Worker
|
64
|
-
def initialize(idx, pid, phase, options)
|
65
|
-
@index = idx
|
66
|
-
@pid = pid
|
67
|
-
@phase = phase
|
68
|
-
@stage = :started
|
69
|
-
@signal = "TERM"
|
70
|
-
@options = options
|
71
|
-
@first_term_sent = nil
|
72
|
-
@last_checkin = Time.now
|
73
|
-
@last_status = '{}'
|
74
|
-
@dead = false
|
75
|
-
end
|
76
|
-
|
77
|
-
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status
|
78
|
-
|
79
|
-
def booted?
|
80
|
-
@stage == :booted
|
81
|
-
end
|
82
|
-
|
83
|
-
def boot!
|
84
|
-
@last_checkin = Time.now
|
85
|
-
@stage = :booted
|
86
|
-
end
|
87
|
-
|
88
|
-
def dead?
|
89
|
-
@dead
|
90
|
-
end
|
91
|
-
|
92
|
-
def dead!
|
93
|
-
@dead = true
|
94
|
-
end
|
95
|
-
|
96
|
-
def ping!(status)
|
97
|
-
@last_checkin = Time.now
|
98
|
-
@last_status = status
|
99
|
-
end
|
100
|
-
|
101
|
-
def ping_timeout?(which)
|
102
|
-
Time.now - @last_checkin > which
|
103
|
-
end
|
104
|
-
|
105
|
-
def term
|
106
|
-
begin
|
107
|
-
if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
|
108
|
-
@signal = "KILL"
|
109
|
-
else
|
110
|
-
@first_term_sent ||= Time.now
|
111
|
-
end
|
112
|
-
|
113
|
-
Process.kill @signal, @pid
|
114
|
-
rescue Errno::ESRCH
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def kill
|
119
|
-
Process.kill "KILL", @pid
|
120
|
-
rescue Errno::ESRCH
|
121
|
-
end
|
122
|
-
|
123
|
-
def hup
|
124
|
-
Process.kill "HUP", @pid
|
125
|
-
rescue Errno::ESRCH
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
63
|
def spawn_workers
|
130
64
|
diff = @options[:workers] - @workers.size
|
131
65
|
return if diff < 1
|
132
66
|
|
133
67
|
master = Process.pid
|
68
|
+
if @options[:fork_worker]
|
69
|
+
@fork_writer << "-1\n"
|
70
|
+
end
|
134
71
|
|
135
72
|
diff.times do
|
136
73
|
idx = next_worker_index
|
137
|
-
@launcher.config.run_hooks :before_worker_fork, idx
|
138
74
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
75
|
+
if @options[:fork_worker] && idx != 0
|
76
|
+
@fork_writer << "#{idx}\n"
|
77
|
+
pid = nil
|
78
|
+
else
|
79
|
+
pid = spawn_worker(idx, master)
|
144
80
|
end
|
145
81
|
|
146
82
|
debug "Spawned worker: #{pid}"
|
147
|
-
@workers <<
|
83
|
+
@workers << WorkerHandle.new(idx, pid, @phase, @options)
|
84
|
+
end
|
148
85
|
|
149
|
-
|
86
|
+
if @options[:fork_worker] &&
|
87
|
+
@workers.all? {|x| x.phase == @phase}
|
88
|
+
|
89
|
+
@fork_writer << "0\n"
|
150
90
|
end
|
91
|
+
end
|
151
92
|
|
152
|
-
|
153
|
-
|
93
|
+
# @version 5.0.0
|
94
|
+
def spawn_worker(idx, master)
|
95
|
+
@launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
|
96
|
+
|
97
|
+
pid = fork { worker(idx, master) }
|
98
|
+
if !pid
|
99
|
+
log "! Complete inability to spawn new workers detected"
|
100
|
+
log "! Seppuku is the only choice."
|
101
|
+
exit! 1
|
154
102
|
end
|
103
|
+
|
104
|
+
@launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
|
105
|
+
pid
|
155
106
|
end
|
156
107
|
|
157
108
|
def cull_workers
|
158
109
|
diff = @workers.size - @options[:workers]
|
159
110
|
return if diff < 1
|
111
|
+
debug "Culling #{diff} workers"
|
160
112
|
|
161
|
-
|
162
|
-
|
163
|
-
workers_to_cull = @workers[-diff,diff]
|
164
|
-
debug "Workers to cull: #{workers_to_cull.inspect}"
|
113
|
+
workers = workers_to_cull(diff)
|
114
|
+
debug "Workers to cull: #{workers.inspect}"
|
165
115
|
|
166
|
-
|
167
|
-
log "- Worker #{worker.index} (
|
116
|
+
workers.each do |worker|
|
117
|
+
log "- Worker #{worker.index} (PID: #{worker.pid}) terminating"
|
168
118
|
worker.term
|
169
119
|
end
|
170
120
|
end
|
171
121
|
|
172
|
-
def
|
173
|
-
|
174
|
-
occupied_positions = @workers.map { |w| w.index }
|
175
|
-
available_positions = all_positions.to_a - occupied_positions
|
176
|
-
available_positions.first
|
177
|
-
end
|
178
|
-
|
179
|
-
def all_workers_booted?
|
180
|
-
@workers.count { |w| !w.booted? } == 0
|
181
|
-
end
|
182
|
-
|
183
|
-
def check_workers(force=false)
|
184
|
-
return if !force && @next_check && @next_check >= Time.now
|
122
|
+
def workers_to_cull(diff)
|
123
|
+
workers = @workers.sort_by(&:started_at)
|
185
124
|
|
186
|
-
|
125
|
+
# In fork_worker mode, worker 0 acts as our master process.
|
126
|
+
# We should avoid culling it to preserve copy-on-write memory gains.
|
127
|
+
workers.reject! { |w| w.index == 0 } if @options[:fork_worker]
|
187
128
|
|
188
|
-
|
129
|
+
workers[cull_start_index(diff), diff]
|
130
|
+
end
|
189
131
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
end
|
132
|
+
def cull_start_index(diff)
|
133
|
+
case @options[:worker_culling_strategy]
|
134
|
+
when :oldest
|
135
|
+
0
|
136
|
+
else # :youngest
|
137
|
+
-diff
|
197
138
|
end
|
139
|
+
end
|
198
140
|
|
199
|
-
|
200
|
-
|
201
|
-
|
141
|
+
# @!attribute [r] next_worker_index
|
142
|
+
def next_worker_index
|
143
|
+
occupied_positions = @workers.map(&:index)
|
144
|
+
idx = 0
|
145
|
+
idx += 1 until !occupied_positions.include?(idx)
|
146
|
+
idx
|
147
|
+
end
|
202
148
|
|
203
|
-
|
204
|
-
|
205
|
-
|
149
|
+
def all_workers_booted?
|
150
|
+
@workers.count { |w| !w.booted? } == 0
|
151
|
+
end
|
206
152
|
|
207
|
-
|
208
|
-
|
153
|
+
def check_workers
|
154
|
+
return if @next_check >= Time.now
|
209
155
|
|
210
|
-
@
|
156
|
+
@next_check = Time.now + @options[:worker_check_interval]
|
211
157
|
|
158
|
+
timeout_workers
|
159
|
+
wait_workers
|
212
160
|
cull_workers
|
213
161
|
spawn_workers
|
214
162
|
|
@@ -220,99 +168,40 @@ module Puma
|
|
220
168
|
w = @workers.find { |x| x.phase != @phase }
|
221
169
|
|
222
170
|
if w
|
223
|
-
|
224
|
-
|
225
|
-
|
171
|
+
log "- Stopping #{w.pid} for phased upgrade..."
|
172
|
+
unless w.term?
|
173
|
+
w.term
|
174
|
+
log "- #{w.signal} sent to #{w.pid}..."
|
226
175
|
end
|
227
|
-
|
228
|
-
w.term
|
229
|
-
log "- #{w.signal} sent to #{w.pid}..."
|
230
176
|
end
|
231
177
|
end
|
232
|
-
end
|
233
178
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
@wakeup.write "!" unless @wakeup.closed?
|
239
|
-
rescue SystemCallError, IOError
|
240
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
241
|
-
end
|
179
|
+
@next_check = [
|
180
|
+
@workers.reject(&:term?).map(&:ping_timeout).min,
|
181
|
+
@next_check
|
182
|
+
].compact.min
|
242
183
|
end
|
243
184
|
|
244
185
|
def worker(index, master)
|
245
|
-
title = "puma: cluster worker #{index}: #{master}"
|
246
|
-
title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
247
|
-
$0 = title
|
248
|
-
|
249
|
-
Signal.trap "SIGINT", "IGNORE"
|
250
|
-
|
251
186
|
@workers = []
|
187
|
+
|
252
188
|
@master_read.close
|
253
189
|
@suicide_pipe.close
|
190
|
+
@fork_writer.close
|
254
191
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
end
|
260
|
-
|
261
|
-
# If we're not running under a Bundler context, then
|
262
|
-
# report the info about the context we will be using
|
263
|
-
if !ENV['BUNDLE_GEMFILE']
|
264
|
-
if File.exist?("Gemfile")
|
265
|
-
log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
|
266
|
-
elsif File.exist?("gems.rb")
|
267
|
-
log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
# Invoke any worker boot hooks so they can get
|
272
|
-
# things in shape before booting the app.
|
273
|
-
@launcher.config.run_hooks :before_worker_boot, index
|
274
|
-
|
275
|
-
server = start_server
|
276
|
-
|
277
|
-
Signal.trap "SIGTERM" do
|
278
|
-
server.stop
|
279
|
-
end
|
280
|
-
|
281
|
-
begin
|
282
|
-
@worker_write << "b#{Process.pid}\n"
|
283
|
-
rescue SystemCallError, IOError
|
284
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
285
|
-
STDERR.puts "Master seems to have exited, exiting."
|
286
|
-
return
|
287
|
-
end
|
288
|
-
|
289
|
-
Thread.new(@worker_write) do |io|
|
290
|
-
base_payload = "p#{Process.pid}"
|
291
|
-
|
292
|
-
while true
|
293
|
-
sleep WORKER_CHECK_INTERVAL
|
294
|
-
begin
|
295
|
-
b = server.backlog || 0
|
296
|
-
r = server.running || 0
|
297
|
-
t = server.pool_capacity || 0
|
298
|
-
m = server.max_threads || 0
|
299
|
-
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m} }\n!
|
300
|
-
io << payload
|
301
|
-
rescue IOError
|
302
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
303
|
-
break
|
304
|
-
end
|
305
|
-
end
|
192
|
+
pipes = { check_pipe: @check_pipe, worker_write: @worker_write }
|
193
|
+
if @options[:fork_worker]
|
194
|
+
pipes[:fork_pipe] = @fork_pipe
|
195
|
+
pipes[:wakeup] = @wakeup
|
306
196
|
end
|
307
197
|
|
308
|
-
server
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
@worker_write.close
|
198
|
+
server = start_server if preload?
|
199
|
+
new_worker = Worker.new index: index,
|
200
|
+
master: master,
|
201
|
+
launcher: @launcher,
|
202
|
+
pipes: pipes,
|
203
|
+
server: server
|
204
|
+
new_worker.run
|
316
205
|
end
|
317
206
|
|
318
207
|
def restart
|
@@ -352,20 +241,63 @@ module Puma
|
|
352
241
|
Dir.chdir dir
|
353
242
|
end
|
354
243
|
|
244
|
+
# Inside of a child process, this will return all zeroes, as @workers is only populated in
|
245
|
+
# the master process.
|
246
|
+
# @!attribute [r] stats
|
355
247
|
def stats
|
356
248
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
357
|
-
|
358
|
-
|
359
|
-
|
249
|
+
worker_status = @workers.map do |w|
|
250
|
+
{
|
251
|
+
started_at: w.started_at.utc.iso8601,
|
252
|
+
pid: w.pid,
|
253
|
+
index: w.index,
|
254
|
+
phase: w.phase,
|
255
|
+
booted: w.booted?,
|
256
|
+
last_checkin: w.last_checkin.utc.iso8601,
|
257
|
+
last_status: w.last_status,
|
258
|
+
}
|
259
|
+
end
|
260
|
+
|
261
|
+
{
|
262
|
+
started_at: @started_at.utc.iso8601,
|
263
|
+
workers: @workers.size,
|
264
|
+
phase: @phase,
|
265
|
+
booted_workers: worker_status.count { |w| w[:booted] },
|
266
|
+
old_workers: old_worker_count,
|
267
|
+
worker_status: worker_status,
|
268
|
+
}
|
360
269
|
end
|
361
270
|
|
362
271
|
def preload?
|
363
272
|
@options[:preload_app]
|
364
273
|
end
|
365
274
|
|
275
|
+
# @version 5.0.0
|
276
|
+
def fork_worker!
|
277
|
+
if (worker = @workers.find { |w| w.index == 0 })
|
278
|
+
worker.phase += 1
|
279
|
+
end
|
280
|
+
phased_restart
|
281
|
+
end
|
282
|
+
|
366
283
|
# We do this in a separate method to keep the lambda scope
|
367
284
|
# of the signals handlers as small as possible.
|
368
285
|
def setup_signals
|
286
|
+
if @options[:fork_worker]
|
287
|
+
Signal.trap "SIGURG" do
|
288
|
+
fork_worker!
|
289
|
+
end
|
290
|
+
|
291
|
+
# Auto-fork after the specified number of requests.
|
292
|
+
if (fork_requests = @options[:fork_worker].to_i) > 0
|
293
|
+
@launcher.events.register(:ping!) do |w|
|
294
|
+
fork_worker! if w.index == 0 &&
|
295
|
+
w.phase == 0 &&
|
296
|
+
w.last_status[:requests_count] >= fork_requests
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
369
301
|
Signal.trap "SIGCHLD" do
|
370
302
|
wakeup!
|
371
303
|
end
|
@@ -390,10 +322,13 @@ module Puma
|
|
390
322
|
log "Early termination of worker"
|
391
323
|
exit! 0
|
392
324
|
else
|
325
|
+
@launcher.close_binder_listeners
|
326
|
+
|
393
327
|
stop_workers
|
394
328
|
stop
|
395
|
-
|
396
|
-
raise
|
329
|
+
@events.fire_on_stopped!
|
330
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
331
|
+
exit 0 # Clean exit, workers were stopped
|
397
332
|
end
|
398
333
|
end
|
399
334
|
end
|
@@ -403,15 +338,25 @@ module Puma
|
|
403
338
|
|
404
339
|
output_header "cluster"
|
405
340
|
|
406
|
-
|
407
|
-
|
408
|
-
before = Thread.list
|
341
|
+
# This is aligned with the output from Runner, see Runner#output_header
|
342
|
+
log "* Workers: #{@options[:workers]}"
|
409
343
|
|
410
344
|
if preload?
|
345
|
+
# Threads explicitly marked as fork safe will be ignored. Used in Rails,
|
346
|
+
# but may be used by anyone. Note that we need to explicit
|
347
|
+
# Process::Waiter check here because there's a bug in Ruby 2.6 and below
|
348
|
+
# where calling thread_variable_get on a Process::Waiter will segfault.
|
349
|
+
# We can drop that clause once those versions of Ruby are no longer
|
350
|
+
# supported.
|
351
|
+
fork_safe = ->(t) { !t.is_a?(Process::Waiter) && t.thread_variable_get(:fork_safe) }
|
352
|
+
|
353
|
+
before = Thread.list.reject(&fork_safe)
|
354
|
+
|
355
|
+
log "* Restarts: (\u2714) hot (\u2716) phased"
|
411
356
|
log "* Preloading application"
|
412
357
|
load_and_bind
|
413
358
|
|
414
|
-
after = Thread.list
|
359
|
+
after = Thread.list.reject(&fork_safe)
|
415
360
|
|
416
361
|
if after.size > before.size
|
417
362
|
threads = (after - before)
|
@@ -425,7 +370,7 @@ module Puma
|
|
425
370
|
end
|
426
371
|
end
|
427
372
|
else
|
428
|
-
log "*
|
373
|
+
log "* Restarts: (\u2714) hot (\u2714) phased"
|
429
374
|
|
430
375
|
unless @launcher.config.app_configured?
|
431
376
|
error "No application configured, nothing to run"
|
@@ -446,12 +391,13 @@ module Puma
|
|
446
391
|
#
|
447
392
|
@check_pipe, @suicide_pipe = Puma::Util.pipe
|
448
393
|
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
394
|
+
# Separate pipe used by worker 0 to receive commands to
|
395
|
+
# fork new worker processes.
|
396
|
+
@fork_pipe, @fork_writer = Puma::Util.pipe
|
397
|
+
|
398
|
+
log "Use Ctrl-C to stop"
|
399
|
+
|
400
|
+
single_worker_warning
|
455
401
|
|
456
402
|
redirect_io
|
457
403
|
|
@@ -463,7 +409,8 @@ module Puma
|
|
463
409
|
|
464
410
|
@master_read, @worker_write = read, @wakeup
|
465
411
|
|
466
|
-
@launcher.config.run_hooks :before_fork, nil
|
412
|
+
@launcher.config.run_hooks :before_fork, nil, @launcher.events
|
413
|
+
Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
|
467
414
|
|
468
415
|
spawn_workers
|
469
416
|
|
@@ -471,48 +418,65 @@ module Puma
|
|
471
418
|
stop
|
472
419
|
end
|
473
420
|
|
474
|
-
@launcher.events.fire_on_booted!
|
475
|
-
|
476
421
|
begin
|
477
|
-
|
422
|
+
booted = false
|
423
|
+
in_phased_restart = false
|
424
|
+
workers_not_booted = @options[:workers]
|
478
425
|
|
479
426
|
while @status == :run
|
480
427
|
begin
|
481
428
|
if @phased_restart
|
482
429
|
start_phased_restart
|
483
430
|
@phased_restart = false
|
431
|
+
in_phased_restart = true
|
432
|
+
workers_not_booted = @options[:workers]
|
484
433
|
end
|
485
434
|
|
486
|
-
check_workers
|
435
|
+
check_workers
|
487
436
|
|
488
|
-
|
489
|
-
|
490
|
-
res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
|
491
|
-
|
492
|
-
if res
|
437
|
+
if read.wait_readable([0, @next_check - Time.now].max)
|
493
438
|
req = read.read_nonblock(1)
|
494
439
|
|
440
|
+
@next_check = Time.now if req == "!"
|
495
441
|
next if !req || req == "!"
|
496
442
|
|
497
443
|
result = read.gets
|
498
444
|
pid = result.to_i
|
499
445
|
|
446
|
+
if req == "b" || req == "f"
|
447
|
+
pid, idx = result.split(':').map(&:to_i)
|
448
|
+
w = @workers.find {|x| x.index == idx}
|
449
|
+
w.pid = pid if w.pid.nil?
|
450
|
+
end
|
451
|
+
|
500
452
|
if w = @workers.find { |x| x.pid == pid }
|
501
453
|
case req
|
502
454
|
when "b"
|
503
455
|
w.boot!
|
504
|
-
log "- Worker #{w.index} (
|
505
|
-
|
456
|
+
log "- Worker #{w.index} (PID: #{pid}) booted in #{w.uptime.round(2)}s, phase: #{w.phase}"
|
457
|
+
@next_check = Time.now
|
458
|
+
workers_not_booted -= 1
|
459
|
+
when "e"
|
460
|
+
# external term, see worker method, Signal.trap "SIGTERM"
|
461
|
+
w.term!
|
506
462
|
when "t"
|
507
|
-
w.
|
508
|
-
force_check = true
|
463
|
+
w.term unless w.term?
|
509
464
|
when "p"
|
510
465
|
w.ping!(result.sub(/^\d+/,'').chomp)
|
466
|
+
@launcher.events.fire(:ping!, w)
|
467
|
+
if !booted && @workers.none? {|worker| worker.last_status.empty?}
|
468
|
+
@launcher.events.fire_on_booted!
|
469
|
+
booted = true
|
470
|
+
end
|
511
471
|
end
|
512
472
|
else
|
513
473
|
log "! Out-of-sync worker list, no #{pid} worker"
|
514
474
|
end
|
515
475
|
end
|
476
|
+
if in_phased_restart && workers_not_booted.zero?
|
477
|
+
@events.fire_on_booted!
|
478
|
+
in_phased_restart = false
|
479
|
+
end
|
516
480
|
|
517
481
|
rescue Interrupt
|
518
482
|
@status = :stop
|
@@ -527,5 +491,56 @@ module Puma
|
|
527
491
|
@wakeup.close
|
528
492
|
end
|
529
493
|
end
|
494
|
+
|
495
|
+
private
|
496
|
+
|
497
|
+
def single_worker_warning
|
498
|
+
return if @options[:workers] != 1 || @options[:silence_single_worker_warning]
|
499
|
+
|
500
|
+
log "! WARNING: Detected running cluster mode with 1 worker."
|
501
|
+
log "! Running Puma in cluster mode with a single worker is often a misconfiguration."
|
502
|
+
log "! Consider running Puma in single-mode (workers = 0) in order to reduce memory overhead."
|
503
|
+
log "! Set the `silence_single_worker_warning` option to silence this warning message."
|
504
|
+
end
|
505
|
+
|
506
|
+
# loops thru @workers, removing workers that exited, and calling
|
507
|
+
# `#term` if needed
|
508
|
+
def wait_workers
|
509
|
+
@workers.reject! do |w|
|
510
|
+
next false if w.pid.nil?
|
511
|
+
begin
|
512
|
+
if Process.wait(w.pid, Process::WNOHANG)
|
513
|
+
true
|
514
|
+
else
|
515
|
+
w.term if w.term?
|
516
|
+
nil
|
517
|
+
end
|
518
|
+
rescue Errno::ECHILD
|
519
|
+
begin
|
520
|
+
Process.kill(0, w.pid)
|
521
|
+
# child still alive but has another parent (e.g., using fork_worker)
|
522
|
+
w.term if w.term?
|
523
|
+
false
|
524
|
+
rescue Errno::ESRCH, Errno::EPERM
|
525
|
+
true # child is already terminated
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
# @version 5.0.0
|
532
|
+
def timeout_workers
|
533
|
+
@workers.each do |w|
|
534
|
+
if !w.term? && w.ping_timeout <= Time.now
|
535
|
+
details = if w.booted?
|
536
|
+
"(worker failed to check in within #{@options[:worker_timeout]} seconds)"
|
537
|
+
else
|
538
|
+
"(worker failed to boot within #{@options[:worker_boot_timeout]} seconds)"
|
539
|
+
end
|
540
|
+
log "! Terminating timed out worker #{details}: #{w.pid}"
|
541
|
+
w.kill
|
542
|
+
end
|
543
|
+
end
|
544
|
+
end
|
530
545
|
end
|
531
546
|
end
|
data/lib/puma/commonlogger.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Puma
|
4
4
|
# Rack::CommonLogger forwards every request to the given +app+, and
|
5
5
|
# logs a line in the
|
6
|
-
# {Apache common log format}[
|
6
|
+
# {Apache common log format}[https://httpd.apache.org/docs/1.3/logs.html#common]
|
7
7
|
# to the +logger+.
|
8
8
|
#
|
9
9
|
# If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
|
@@ -16,7 +16,7 @@ module Puma
|
|
16
16
|
# (which is called without arguments in order to make the error appear for
|
17
17
|
# sure)
|
18
18
|
class CommonLogger
|
19
|
-
# Common Log Format:
|
19
|
+
# Common Log Format: https://httpd.apache.org/docs/1.3/logs.html#common
|
20
20
|
#
|
21
21
|
# lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
|
22
22
|
#
|