puma 4.1.0 → 4.2.0
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.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +38 -7
- data/README.md +5 -16
- data/ext/puma_http11/http11_parser.c +37 -62
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/lib/puma.rb +6 -0
- data/lib/puma/accept_nonblock.rb +5 -1
- data/lib/puma/app/status.rb +29 -28
- data/lib/puma/binder.rb +32 -10
- data/lib/puma/cli.rb +4 -0
- data/lib/puma/client.rb +191 -203
- data/lib/puma/cluster.rb +42 -45
- data/lib/puma/const.rb +15 -18
- data/lib/puma/control_cli.rb +11 -2
- data/lib/puma/dsl.rb +16 -0
- data/lib/puma/events.rb +2 -2
- data/lib/puma/launcher.rb +90 -46
- data/lib/puma/plugin.rb +5 -2
- data/lib/puma/reactor.rb +5 -4
- data/lib/puma/runner.rb +3 -3
- data/lib/puma/server.rb +11 -5
- data/lib/puma/single.rb +1 -0
- data/lib/puma/thread_pool.rb +9 -31
- data/lib/rack/handler/puma.rb +0 -2
- data/tools/docker/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +4 -3
- data/lib/puma/daemon_ext.rb +0 -33
data/lib/puma/cluster.rb
CHANGED
@@ -35,34 +35,10 @@ module Puma
|
|
35
35
|
@workers.each { |x| x.term }
|
36
36
|
|
37
37
|
begin
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
rescue Errno::ECHILD
|
43
|
-
# child is already terminated
|
44
|
-
end
|
45
|
-
end
|
46
|
-
else
|
47
|
-
# below code is for a bug in Ruby 2.6+, above waitpid call hangs
|
48
|
-
t_st = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
49
|
-
pids = @workers.map(&:pid)
|
50
|
-
loop do
|
51
|
-
pids.reject! do |w_pid|
|
52
|
-
begin
|
53
|
-
if Process.waitpid(w_pid, Process::WNOHANG)
|
54
|
-
log " worker status: #{$?}"
|
55
|
-
true
|
56
|
-
end
|
57
|
-
rescue Errno::ECHILD
|
58
|
-
true # child is already terminated
|
59
|
-
end
|
60
|
-
end
|
61
|
-
break if pids.empty?
|
62
|
-
sleep 0.5
|
63
|
-
end
|
64
|
-
t_end = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
65
|
-
log format(" worker shutdown time: %6.2f", t_end - t_st)
|
38
|
+
loop do
|
39
|
+
wait_workers
|
40
|
+
break if @workers.empty?
|
41
|
+
sleep 0.2
|
66
42
|
end
|
67
43
|
rescue Interrupt
|
68
44
|
log "! Cancelled waiting for workers"
|
@@ -98,7 +74,7 @@ module Puma
|
|
98
74
|
@started_at = Time.now
|
99
75
|
@last_checkin = Time.now
|
100
76
|
@last_status = '{}'
|
101
|
-
@
|
77
|
+
@term = false
|
102
78
|
end
|
103
79
|
|
104
80
|
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
@@ -112,12 +88,8 @@ module Puma
|
|
112
88
|
@stage = :booted
|
113
89
|
end
|
114
90
|
|
115
|
-
def
|
116
|
-
@
|
117
|
-
end
|
118
|
-
|
119
|
-
def dead!
|
120
|
-
@dead = true
|
91
|
+
def term?
|
92
|
+
@term
|
121
93
|
end
|
122
94
|
|
123
95
|
def ping!(status)
|
@@ -134,9 +106,9 @@ module Puma
|
|
134
106
|
if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
|
135
107
|
@signal = "KILL"
|
136
108
|
else
|
109
|
+
@term ||= true
|
137
110
|
@first_term_sent ||= Time.now
|
138
111
|
end
|
139
|
-
|
140
112
|
Process.kill @signal, @pid
|
141
113
|
rescue Errno::ESRCH
|
142
114
|
end
|
@@ -227,12 +199,7 @@ module Puma
|
|
227
199
|
# during this loop by giving the kernel time to kill them.
|
228
200
|
sleep 1 if any
|
229
201
|
|
230
|
-
|
231
|
-
while pid = Process.waitpid(-1, Process::WNOHANG) do
|
232
|
-
pids << pid
|
233
|
-
end
|
234
|
-
@workers.reject! { |w| w.dead? || pids.include?(w.pid) }
|
235
|
-
|
202
|
+
wait_workers
|
236
203
|
cull_workers
|
237
204
|
spawn_workers
|
238
205
|
|
@@ -249,8 +216,10 @@ module Puma
|
|
249
216
|
log "- Stopping #{w.pid} for phased upgrade..."
|
250
217
|
end
|
251
218
|
|
252
|
-
w.term
|
253
|
-
|
219
|
+
unless w.term?
|
220
|
+
w.term
|
221
|
+
log "- #{w.signal} sent to #{w.pid}..."
|
222
|
+
end
|
254
223
|
end
|
255
224
|
end
|
256
225
|
end
|
@@ -277,6 +246,7 @@ module Puma
|
|
277
246
|
@suicide_pipe.close
|
278
247
|
|
279
248
|
Thread.new do
|
249
|
+
Puma.set_thread_name "worker check pipe"
|
280
250
|
IO.select [@check_pipe]
|
281
251
|
log "! Detected parent died, dying"
|
282
252
|
exit! 1
|
@@ -299,6 +269,7 @@ module Puma
|
|
299
269
|
server = start_server
|
300
270
|
|
301
271
|
Signal.trap "SIGTERM" do
|
272
|
+
@worker_write << "e#{Process.pid}\n" rescue nil
|
302
273
|
server.stop
|
303
274
|
end
|
304
275
|
|
@@ -311,6 +282,7 @@ module Puma
|
|
311
282
|
end
|
312
283
|
|
313
284
|
Thread.new(@worker_write) do |io|
|
285
|
+
Puma.set_thread_name "stat payload"
|
314
286
|
base_payload = "p#{Process.pid}"
|
315
287
|
|
316
288
|
while true
|
@@ -376,6 +348,8 @@ module Puma
|
|
376
348
|
Dir.chdir dir
|
377
349
|
end
|
378
350
|
|
351
|
+
# Inside of a child process, this will return all zeroes, as @workers is only populated in
|
352
|
+
# the master process.
|
379
353
|
def stats
|
380
354
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
381
355
|
booted_worker_count = @workers.count { |w| w.booted? }
|
@@ -530,8 +504,11 @@ module Puma
|
|
530
504
|
w.boot!
|
531
505
|
log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
|
532
506
|
force_check = true
|
507
|
+
when "e"
|
508
|
+
# external term, see worker method, Signal.trap "SIGTERM"
|
509
|
+
w.instance_variable_set :@term, true
|
533
510
|
when "t"
|
534
|
-
w.
|
511
|
+
w.term unless w.term?
|
535
512
|
force_check = true
|
536
513
|
when "p"
|
537
514
|
w.ping!(result.sub(/^\d+/,'').chomp)
|
@@ -552,6 +529,26 @@ module Puma
|
|
552
529
|
@suicide_pipe.close
|
553
530
|
read.close
|
554
531
|
@wakeup.close
|
532
|
+
@launcher.close_binder_unix_paths
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
private
|
537
|
+
|
538
|
+
# loops thru @workers, removing workers that exited, and calling
|
539
|
+
# `#term` if needed
|
540
|
+
def wait_workers
|
541
|
+
@workers.reject! do |w|
|
542
|
+
begin
|
543
|
+
if Process.wait(w.pid, Process::WNOHANG)
|
544
|
+
true
|
545
|
+
else
|
546
|
+
w.term if w.term?
|
547
|
+
nil
|
548
|
+
end
|
549
|
+
rescue Errno::ECHILD
|
550
|
+
true # child is already terminated
|
551
|
+
end
|
555
552
|
end
|
556
553
|
end
|
557
554
|
end
|
data/lib/puma/const.rb
CHANGED
@@ -100,8 +100,8 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "4.
|
104
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "4.2.0".freeze
|
104
|
+
CODE_NAME = "Distant Airhorns".freeze
|
105
105
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
106
106
|
|
107
107
|
FAST_TRACK_KA_TIMEOUT = 0.2
|
@@ -122,27 +122,24 @@ module Puma
|
|
122
122
|
REQUEST_URI= 'REQUEST_URI'.freeze
|
123
123
|
REQUEST_PATH = 'REQUEST_PATH'.freeze
|
124
124
|
QUERY_STRING = 'QUERY_STRING'.freeze
|
125
|
+
CONTENT_LENGTH = "CONTENT_LENGTH".freeze
|
125
126
|
|
126
127
|
PATH_INFO = 'PATH_INFO'.freeze
|
127
128
|
|
128
129
|
PUMA_TMP_BASE = "puma".freeze
|
129
130
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
|
143
|
-
|
144
|
-
# A common header for indicating the server is too busy. Not used yet.
|
145
|
-
ERROR_503_RESPONSE = "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
|
131
|
+
ERROR_RESPONSE = {
|
132
|
+
# Indicate that we couldn't parse the request
|
133
|
+
400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
|
134
|
+
# The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
|
135
|
+
404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,
|
136
|
+
# The standard empty 408 response for requests that timed out.
|
137
|
+
408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
|
138
|
+
# Indicate that there was an internal error, obviously.
|
139
|
+
500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
|
140
|
+
# A common header for indicating the server is too busy. Not used yet.
|
141
|
+
503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
|
142
|
+
}
|
146
143
|
|
147
144
|
# The basic max request size we'll try to read.
|
148
145
|
CHUNK_SIZE = 16 * 1024
|
data/lib/puma/control_cli.rb
CHANGED
@@ -22,6 +22,7 @@ module Puma
|
|
22
22
|
@control_auth_token = nil
|
23
23
|
@config_file = nil
|
24
24
|
@command = nil
|
25
|
+
@environment = ENV['RACK_ENV'] || "development"
|
25
26
|
|
26
27
|
@argv = argv.dup
|
27
28
|
@stdout = stdout
|
@@ -59,6 +60,11 @@ module Puma
|
|
59
60
|
@config_file = arg
|
60
61
|
end
|
61
62
|
|
63
|
+
o.on "-e", "--environment ENVIRONMENT",
|
64
|
+
"The environment to run the Rack app on (default development)" do |arg|
|
65
|
+
@environment = arg
|
66
|
+
end
|
67
|
+
|
62
68
|
o.on_tail("-H", "--help", "Show this message") do
|
63
69
|
@stdout.puts o
|
64
70
|
exit
|
@@ -76,8 +82,10 @@ module Puma
|
|
76
82
|
@command = argv.shift
|
77
83
|
|
78
84
|
unless @config_file == '-'
|
79
|
-
if @config_file.nil?
|
80
|
-
@config_file =
|
85
|
+
if @config_file.nil?
|
86
|
+
@config_file = %W(config/puma/#{@environment}.rb config/puma.rb).find do |f|
|
87
|
+
File.exist?(f)
|
88
|
+
end
|
81
89
|
end
|
82
90
|
|
83
91
|
if @config_file
|
@@ -258,6 +266,7 @@ module Puma
|
|
258
266
|
run_args += ["--control-url", @control_url] if @control_url
|
259
267
|
run_args += ["--control-token", @control_auth_token] if @control_auth_token
|
260
268
|
run_args += ["-C", @config_file] if @config_file
|
269
|
+
run_args += ["-e", @environment] if @environment
|
261
270
|
|
262
271
|
events = Puma::Events.new @stdout, @stderr
|
263
272
|
|
data/lib/puma/dsl.rb
CHANGED
@@ -584,6 +584,7 @@ module Puma
|
|
584
584
|
# dictates.
|
585
585
|
#
|
586
586
|
# @note This is incompatible with +preload_app!+.
|
587
|
+
# @note This is only supported for RubyGems 2.2+
|
587
588
|
def prune_bundler(answer=true)
|
588
589
|
@options[:prune_bundler] = answer
|
589
590
|
end
|
@@ -601,6 +602,21 @@ module Puma
|
|
601
602
|
@options[:raise_exception_on_sigterm] = answer
|
602
603
|
end
|
603
604
|
|
605
|
+
# When using prune_bundler, if extra runtime dependencies need to be loaded to
|
606
|
+
# initialize your app, then this setting can be used.
|
607
|
+
#
|
608
|
+
# Before bundler is pruned, the gem names supplied will be looked up in the bundler
|
609
|
+
# context and then loaded again after bundler is pruned.
|
610
|
+
# Only applies if prune_bundler is used.
|
611
|
+
#
|
612
|
+
# @example
|
613
|
+
# extra_runtime_dependencies ['gem_name_1', 'gem_name_2']
|
614
|
+
# @example
|
615
|
+
# extra_runtime_dependencies ['puma_worker_killer']
|
616
|
+
def extra_runtime_dependencies(answer = [])
|
617
|
+
@options[:extra_runtime_dependencies] = Array(answer)
|
618
|
+
end
|
619
|
+
|
604
620
|
# Additional text to display in process listing.
|
605
621
|
#
|
606
622
|
# If you do not specify a tag, Puma will infer it. If you do not want Puma
|
data/lib/puma/events.rb
CHANGED
data/lib/puma/launcher.rb
CHANGED
@@ -2,12 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'puma/events'
|
4
4
|
require 'puma/detect'
|
5
|
-
|
6
5
|
require 'puma/cluster'
|
7
6
|
require 'puma/single'
|
8
|
-
|
9
7
|
require 'puma/const'
|
10
|
-
|
11
8
|
require 'puma/binder'
|
12
9
|
|
13
10
|
module Puma
|
@@ -126,19 +123,6 @@ module Puma
|
|
126
123
|
File.unlink(path) if path && File.exist?(path)
|
127
124
|
end
|
128
125
|
|
129
|
-
# If configured, write the pid of the current process out
|
130
|
-
# to a file.
|
131
|
-
def write_pid
|
132
|
-
path = @options[:pidfile]
|
133
|
-
return unless path
|
134
|
-
|
135
|
-
File.open(path, 'w') { |f| f.puts Process.pid }
|
136
|
-
cur = Process.pid
|
137
|
-
at_exit do
|
138
|
-
delete_pidfile if cur == Process.pid
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
126
|
# Begin async shutdown of the server
|
143
127
|
def halt
|
144
128
|
@status = :halt
|
@@ -217,16 +201,28 @@ module Puma
|
|
217
201
|
end
|
218
202
|
|
219
203
|
def close_binder_listeners
|
220
|
-
@binder.
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
end
|
204
|
+
@binder.close_listeners
|
205
|
+
end
|
206
|
+
|
207
|
+
def close_binder_unix_paths
|
208
|
+
@binder.close_unix_paths
|
226
209
|
end
|
227
210
|
|
228
211
|
private
|
229
212
|
|
213
|
+
# If configured, write the pid of the current process out
|
214
|
+
# to a file.
|
215
|
+
def write_pid
|
216
|
+
path = @options[:pidfile]
|
217
|
+
return unless path
|
218
|
+
|
219
|
+
File.open(path, 'w') { |f| f.puts Process.pid }
|
220
|
+
cur = Process.pid
|
221
|
+
at_exit do
|
222
|
+
delete_pidfile if cur == Process.pid
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
230
226
|
def reload_worker_directory
|
231
227
|
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
232
228
|
end
|
@@ -246,48 +242,71 @@ module Puma
|
|
246
242
|
Dir.chdir(@restart_dir)
|
247
243
|
Kernel.exec(*argv)
|
248
244
|
else
|
249
|
-
redirects = {:close_others => true}
|
250
|
-
@binder.listeners.each_with_index do |(l, io), i|
|
251
|
-
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
252
|
-
redirects[io.to_i] = io.to_i
|
253
|
-
end
|
254
|
-
|
255
245
|
argv = restart_args
|
256
246
|
Dir.chdir(@restart_dir)
|
257
|
-
argv += [
|
247
|
+
argv += [@binder.redirects_for_restart]
|
258
248
|
Kernel.exec(*argv)
|
259
249
|
end
|
260
250
|
end
|
261
251
|
|
262
|
-
def
|
263
|
-
|
264
|
-
|
265
|
-
|
252
|
+
def dependencies_and_files_to_require_after_prune
|
253
|
+
puma = spec_for_gem("puma")
|
254
|
+
|
255
|
+
deps = puma.runtime_dependencies.map do |d|
|
256
|
+
"#{d.name}:#{spec_for_gem(d.name).version}"
|
257
|
+
end
|
258
|
+
|
259
|
+
[deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
|
260
|
+
end
|
261
|
+
|
262
|
+
def extra_runtime_deps_directories
|
263
|
+
Array(@options[:extra_runtime_dependencies]).map do |d_name|
|
264
|
+
if (spec = spec_for_gem(d_name))
|
265
|
+
require_paths_for_gem(spec)
|
266
|
+
else
|
267
|
+
log "* Could not load extra dependency: #{d_name}"
|
268
|
+
nil
|
269
|
+
end
|
270
|
+
end.flatten.compact
|
271
|
+
end
|
272
|
+
|
273
|
+
def puma_wild_location
|
274
|
+
puma = spec_for_gem("puma")
|
275
|
+
dirs = require_paths_for_gem(puma)
|
266
276
|
puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
|
277
|
+
File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
|
278
|
+
end
|
267
279
|
|
268
|
-
|
280
|
+
def prune_bundler
|
281
|
+
return unless defined?(Bundler)
|
282
|
+
require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
|
283
|
+
unless puma_wild_location
|
269
284
|
log "! Unable to prune Bundler environment, continuing"
|
270
285
|
return
|
271
286
|
end
|
272
287
|
|
273
|
-
deps =
|
274
|
-
spec = Bundler.rubygems.loaded_specs(d.name)
|
275
|
-
"#{d.name}:#{spec.version.to_s}"
|
276
|
-
end
|
288
|
+
deps, dirs = dependencies_and_files_to_require_after_prune
|
277
289
|
|
278
290
|
log '* Pruning Bundler environment'
|
279
291
|
home = ENV['GEM_HOME']
|
280
292
|
Bundler.with_clean_env do
|
281
293
|
ENV['GEM_HOME'] = home
|
282
294
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
283
|
-
|
284
|
-
args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
295
|
+
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
285
296
|
# Ruby 2.0+ defaults to true which breaks socket activation
|
286
297
|
args += [{:close_others => false}]
|
287
298
|
Kernel.exec(*args)
|
288
299
|
end
|
289
300
|
end
|
290
301
|
|
302
|
+
def spec_for_gem(gem_name)
|
303
|
+
Bundler.rubygems.loaded_specs(gem_name)
|
304
|
+
end
|
305
|
+
|
306
|
+
def require_paths_for_gem(gem_spec)
|
307
|
+
gem_spec.full_require_paths
|
308
|
+
end
|
309
|
+
|
291
310
|
def log(str)
|
292
311
|
@events.log str
|
293
312
|
end
|
@@ -307,6 +326,21 @@ module Puma
|
|
307
326
|
log "- Goodbye!"
|
308
327
|
end
|
309
328
|
|
329
|
+
def log_thread_status
|
330
|
+
Thread.list.each do |thread|
|
331
|
+
log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
|
332
|
+
logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
|
333
|
+
logstr += " #{thread.name}" if thread.respond_to?(:name)
|
334
|
+
log logstr
|
335
|
+
|
336
|
+
if thread.backtrace
|
337
|
+
log thread.backtrace.join("\n")
|
338
|
+
else
|
339
|
+
log "<no backtrace available>"
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
310
344
|
def set_process_title
|
311
345
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
312
346
|
end
|
@@ -406,12 +440,6 @@ module Puma
|
|
406
440
|
|
407
441
|
begin
|
408
442
|
Signal.trap "SIGINT" do
|
409
|
-
if Puma.jruby?
|
410
|
-
@status = :exit
|
411
|
-
graceful_stop
|
412
|
-
exit
|
413
|
-
end
|
414
|
-
|
415
443
|
stop
|
416
444
|
end
|
417
445
|
rescue Exception
|
@@ -429,6 +457,22 @@ module Puma
|
|
429
457
|
rescue Exception
|
430
458
|
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
|
431
459
|
end
|
460
|
+
|
461
|
+
begin
|
462
|
+
Signal.trap "SIGINFO" do
|
463
|
+
log_thread_status
|
464
|
+
end
|
465
|
+
rescue Exception
|
466
|
+
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
|
467
|
+
# to see this constantly on Linux.
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
def require_rubygems_min_version!(min_version, feature)
|
472
|
+
return if min_version <= Gem::Version.new(Gem::VERSION)
|
473
|
+
|
474
|
+
raise "#{feature} is not supported on your version of RubyGems. " \
|
475
|
+
"You must have RubyGems #{min_version}+ to use this feature."
|
432
476
|
end
|
433
477
|
end
|
434
478
|
end
|