puma 4.1.1 → 4.3.6
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 +71 -10
- data/README.md +8 -37
- data/docs/plugins.md +20 -10
- data/docs/tcp_mode.md +96 -0
- data/ext/puma_http11/extconf.rb +5 -0
- data/ext/puma_http11/http11_parser.c +40 -63
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser.rl +3 -1
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +91 -106
- data/ext/puma_http11/puma_http11.c +3 -0
- data/lib/puma.rb +6 -0
- data/lib/puma/app/status.rb +33 -30
- data/lib/puma/binder.rb +38 -70
- data/lib/puma/cli.rb +4 -0
- data/lib/puma/client.rb +202 -206
- data/lib/puma/cluster.rb +13 -12
- data/lib/puma/const.rb +23 -18
- data/lib/puma/control_cli.rb +20 -3
- data/lib/puma/dsl.rb +19 -1
- data/lib/puma/launcher.rb +87 -46
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/plugin.rb +5 -2
- data/lib/puma/reactor.rb +7 -5
- data/lib/puma/runner.rb +10 -3
- data/lib/puma/server.rb +68 -12
- data/lib/puma/thread_pool.rb +10 -32
- data/lib/rack/handler/puma.rb +0 -2
- data/tools/docker/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +10 -9
- 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/cluster.rb
CHANGED
@@ -74,7 +74,6 @@ module Puma
|
|
74
74
|
@started_at = Time.now
|
75
75
|
@last_checkin = Time.now
|
76
76
|
@last_status = '{}'
|
77
|
-
@dead = false
|
78
77
|
@term = false
|
79
78
|
end
|
80
79
|
|
@@ -89,14 +88,6 @@ module Puma
|
|
89
88
|
@stage = :booted
|
90
89
|
end
|
91
90
|
|
92
|
-
def dead?
|
93
|
-
@dead
|
94
|
-
end
|
95
|
-
|
96
|
-
def dead!
|
97
|
-
@dead = true
|
98
|
-
end
|
99
|
-
|
100
91
|
def term?
|
101
92
|
@term
|
102
93
|
end
|
@@ -225,8 +216,10 @@ module Puma
|
|
225
216
|
log "- Stopping #{w.pid} for phased upgrade..."
|
226
217
|
end
|
227
218
|
|
228
|
-
w.term
|
229
|
-
|
219
|
+
unless w.term?
|
220
|
+
w.term
|
221
|
+
log "- #{w.signal} sent to #{w.pid}..."
|
222
|
+
end
|
230
223
|
end
|
231
224
|
end
|
232
225
|
end
|
@@ -253,6 +246,7 @@ module Puma
|
|
253
246
|
@suicide_pipe.close
|
254
247
|
|
255
248
|
Thread.new do
|
249
|
+
Puma.set_thread_name "worker check pipe"
|
256
250
|
IO.select [@check_pipe]
|
257
251
|
log "! Detected parent died, dying"
|
258
252
|
exit! 1
|
@@ -275,6 +269,7 @@ module Puma
|
|
275
269
|
server = start_server
|
276
270
|
|
277
271
|
Signal.trap "SIGTERM" do
|
272
|
+
@worker_write << "e#{Process.pid}\n" rescue nil
|
278
273
|
server.stop
|
279
274
|
end
|
280
275
|
|
@@ -287,6 +282,7 @@ module Puma
|
|
287
282
|
end
|
288
283
|
|
289
284
|
Thread.new(@worker_write) do |io|
|
285
|
+
Puma.set_thread_name "stat payload"
|
290
286
|
base_payload = "p#{Process.pid}"
|
291
287
|
|
292
288
|
while true
|
@@ -352,6 +348,8 @@ module Puma
|
|
352
348
|
Dir.chdir dir
|
353
349
|
end
|
354
350
|
|
351
|
+
# Inside of a child process, this will return all zeroes, as @workers is only populated in
|
352
|
+
# the master process.
|
355
353
|
def stats
|
356
354
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
357
355
|
booted_worker_count = @workers.count { |w| w.booted? }
|
@@ -506,8 +504,11 @@ module Puma
|
|
506
504
|
w.boot!
|
507
505
|
log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
|
508
506
|
force_check = true
|
507
|
+
when "e"
|
508
|
+
# external term, see worker method, Signal.trap "SIGTERM"
|
509
|
+
w.instance_variable_set :@term, true
|
509
510
|
when "t"
|
510
|
-
w.
|
511
|
+
w.term unless w.term?
|
511
512
|
force_check = true
|
512
513
|
when "p"
|
513
514
|
w.ping!(result.sub(/^\d+/,'').chomp)
|
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.3.6".freeze
|
104
|
+
CODE_NAME = "Mysterious Traveller".freeze
|
105
105
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
106
106
|
|
107
107
|
FAST_TRACK_KA_TIMEOUT = 0.2
|
@@ -118,31 +118,35 @@ module Puma
|
|
118
118
|
# sending data back
|
119
119
|
WRITE_TIMEOUT = 10
|
120
120
|
|
121
|
+
# How many requests to attempt inline before sending a client back to
|
122
|
+
# the reactor to be subject to normal ordering. The idea here is that
|
123
|
+
# we amortize the cost of going back to the reactor for a well behaved
|
124
|
+
# but very "greedy" client across 10 requests. This prevents a not
|
125
|
+
# well behaved client from monopolizing the thread forever.
|
126
|
+
MAX_FAST_INLINE = 10
|
127
|
+
|
121
128
|
# The original URI requested by the client.
|
122
129
|
REQUEST_URI= 'REQUEST_URI'.freeze
|
123
130
|
REQUEST_PATH = 'REQUEST_PATH'.freeze
|
124
131
|
QUERY_STRING = 'QUERY_STRING'.freeze
|
132
|
+
CONTENT_LENGTH = "CONTENT_LENGTH".freeze
|
125
133
|
|
126
134
|
PATH_INFO = 'PATH_INFO'.freeze
|
127
135
|
|
128
136
|
PUMA_TMP_BASE = "puma".freeze
|
129
137
|
|
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
|
138
|
+
ERROR_RESPONSE = {
|
139
|
+
# Indicate that we couldn't parse the request
|
140
|
+
400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
|
141
|
+
# The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
|
142
|
+
404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,
|
143
|
+
# The standard empty 408 response for requests that timed out.
|
144
|
+
408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
|
145
|
+
# Indicate that there was an internal error, obviously.
|
146
|
+
500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
|
147
|
+
# A common header for indicating the server is too busy. Not used yet.
|
148
|
+
503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
|
149
|
+
}
|
146
150
|
|
147
151
|
# The basic max request size we'll try to read.
|
148
152
|
CHUNK_SIZE = 16 * 1024
|
@@ -224,6 +228,7 @@ module Puma
|
|
224
228
|
COLON = ": ".freeze
|
225
229
|
|
226
230
|
NEWLINE = "\n".freeze
|
231
|
+
HTTP_INJECTION_REGEX = /[\r\n]/.freeze
|
227
232
|
|
228
233
|
HIJACK_P = "rack.hijack?".freeze
|
229
234
|
HIJACK = "rack.hijack".freeze
|
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']
|
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,12 @@ module Puma
|
|
76
82
|
@command = argv.shift
|
77
83
|
|
78
84
|
unless @config_file == '-'
|
79
|
-
|
80
|
-
|
85
|
+
environment = @environment || 'development'
|
86
|
+
|
87
|
+
if @config_file.nil?
|
88
|
+
@config_file = %W(config/puma/#{environment}.rb config/puma.rb).find do |f|
|
89
|
+
File.exist?(f)
|
90
|
+
end
|
81
91
|
end
|
82
92
|
|
83
93
|
if @config_file
|
@@ -122,7 +132,7 @@ module Puma
|
|
122
132
|
@pid = sf.pid
|
123
133
|
elsif @pidfile
|
124
134
|
# get pid from pid_file
|
125
|
-
|
135
|
+
File.open(@pidfile) { |f| @pid = f.read.to_i }
|
126
136
|
end
|
127
137
|
end
|
128
138
|
|
@@ -131,6 +141,12 @@ module Puma
|
|
131
141
|
|
132
142
|
# create server object by scheme
|
133
143
|
server = case uri.scheme
|
144
|
+
when "ssl"
|
145
|
+
require 'openssl'
|
146
|
+
OpenSSL::SSL::SSLSocket.new(
|
147
|
+
TCPSocket.new(uri.host, uri.port),
|
148
|
+
OpenSSL::SSL::SSLContext.new
|
149
|
+
).tap(&:connect)
|
134
150
|
when "tcp"
|
135
151
|
TCPSocket.new uri.host, uri.port
|
136
152
|
when "unix"
|
@@ -258,6 +274,7 @@ module Puma
|
|
258
274
|
run_args += ["--control-url", @control_url] if @control_url
|
259
275
|
run_args += ["--control-token", @control_auth_token] if @control_auth_token
|
260
276
|
run_args += ["-C", @config_file] if @config_file
|
277
|
+
run_args += ["-e", @environment] if @environment
|
261
278
|
|
262
279
|
events = Puma::Events.new @stdout, @stderr
|
263
280
|
|
data/lib/puma/dsl.rb
CHANGED
@@ -396,7 +396,7 @@ module Puma
|
|
396
396
|
# keystore_pass: password
|
397
397
|
# }
|
398
398
|
def ssl_bind(host, port, opts)
|
399
|
-
verify = opts.fetch(:verify_mode, 'none')
|
399
|
+
verify = opts.fetch(:verify_mode, 'none').to_s
|
400
400
|
no_tlsv1 = opts.fetch(:no_tlsv1, 'false')
|
401
401
|
no_tlsv1_1 = opts.fetch(:no_tlsv1_1, 'false')
|
402
402
|
ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
|
@@ -583,7 +583,10 @@ module Puma
|
|
583
583
|
# new Bundler context and thus can float around as the release
|
584
584
|
# dictates.
|
585
585
|
#
|
586
|
+
# See also: extra_runtime_dependencies
|
587
|
+
#
|
586
588
|
# @note This is incompatible with +preload_app!+.
|
589
|
+
# @note This is only supported for RubyGems 2.2+
|
587
590
|
def prune_bundler(answer=true)
|
588
591
|
@options[:prune_bundler] = answer
|
589
592
|
end
|
@@ -601,6 +604,21 @@ module Puma
|
|
601
604
|
@options[:raise_exception_on_sigterm] = answer
|
602
605
|
end
|
603
606
|
|
607
|
+
# When using prune_bundler, if extra runtime dependencies need to be loaded to
|
608
|
+
# initialize your app, then this setting can be used. This includes any Puma plugins.
|
609
|
+
#
|
610
|
+
# Before bundler is pruned, the gem names supplied will be looked up in the bundler
|
611
|
+
# context and then loaded again after bundler is pruned.
|
612
|
+
# Only applies if prune_bundler is used.
|
613
|
+
#
|
614
|
+
# @example
|
615
|
+
# extra_runtime_dependencies ['gem_name_1', 'gem_name_2']
|
616
|
+
# @example
|
617
|
+
# extra_runtime_dependencies ['puma_worker_killer', 'puma-heroku']
|
618
|
+
def extra_runtime_dependencies(answer = [])
|
619
|
+
@options[:extra_runtime_dependencies] = Array(answer)
|
620
|
+
end
|
621
|
+
|
604
622
|
# Additional text to display in process listing.
|
605
623
|
#
|
606
624
|
# If you do not specify a tag, Puma will infer it. If you do not want Puma
|
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
|
@@ -200,6 +184,7 @@ module Puma
|
|
200
184
|
when :exit
|
201
185
|
# nothing
|
202
186
|
end
|
187
|
+
@binder.close_unix_paths
|
203
188
|
end
|
204
189
|
|
205
190
|
# Return which tcp port the launcher is using, if it's using TCP
|
@@ -217,16 +202,24 @@ module Puma
|
|
217
202
|
end
|
218
203
|
|
219
204
|
def close_binder_listeners
|
220
|
-
@binder.
|
221
|
-
io.close
|
222
|
-
uri = URI.parse(l)
|
223
|
-
next unless uri.scheme == 'unix'
|
224
|
-
File.unlink("#{uri.host}#{uri.path}")
|
225
|
-
end
|
205
|
+
@binder.close_listeners
|
226
206
|
end
|
227
207
|
|
228
208
|
private
|
229
209
|
|
210
|
+
# If configured, write the pid of the current process out
|
211
|
+
# to a file.
|
212
|
+
def write_pid
|
213
|
+
path = @options[:pidfile]
|
214
|
+
return unless path
|
215
|
+
|
216
|
+
File.open(path, 'w') { |f| f.puts Process.pid }
|
217
|
+
cur = Process.pid
|
218
|
+
at_exit do
|
219
|
+
delete_pidfile if cur == Process.pid
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
230
223
|
def reload_worker_directory
|
231
224
|
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
232
225
|
end
|
@@ -246,48 +239,71 @@ module Puma
|
|
246
239
|
Dir.chdir(@restart_dir)
|
247
240
|
Kernel.exec(*argv)
|
248
241
|
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
242
|
argv = restart_args
|
256
243
|
Dir.chdir(@restart_dir)
|
257
|
-
argv += [
|
244
|
+
argv += [@binder.redirects_for_restart]
|
258
245
|
Kernel.exec(*argv)
|
259
246
|
end
|
260
247
|
end
|
261
248
|
|
262
|
-
def
|
263
|
-
|
264
|
-
|
265
|
-
|
249
|
+
def dependencies_and_files_to_require_after_prune
|
250
|
+
puma = spec_for_gem("puma")
|
251
|
+
|
252
|
+
deps = puma.runtime_dependencies.map do |d|
|
253
|
+
"#{d.name}:#{spec_for_gem(d.name).version}"
|
254
|
+
end
|
255
|
+
|
256
|
+
[deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
|
257
|
+
end
|
258
|
+
|
259
|
+
def extra_runtime_deps_directories
|
260
|
+
Array(@options[:extra_runtime_dependencies]).map do |d_name|
|
261
|
+
if (spec = spec_for_gem(d_name))
|
262
|
+
require_paths_for_gem(spec)
|
263
|
+
else
|
264
|
+
log "* Could not load extra dependency: #{d_name}"
|
265
|
+
nil
|
266
|
+
end
|
267
|
+
end.flatten.compact
|
268
|
+
end
|
269
|
+
|
270
|
+
def puma_wild_location
|
271
|
+
puma = spec_for_gem("puma")
|
272
|
+
dirs = require_paths_for_gem(puma)
|
266
273
|
puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
|
274
|
+
File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
|
275
|
+
end
|
267
276
|
|
268
|
-
|
277
|
+
def prune_bundler
|
278
|
+
return unless defined?(Bundler)
|
279
|
+
require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
|
280
|
+
unless puma_wild_location
|
269
281
|
log "! Unable to prune Bundler environment, continuing"
|
270
282
|
return
|
271
283
|
end
|
272
284
|
|
273
|
-
deps =
|
274
|
-
spec = Bundler.rubygems.loaded_specs(d.name)
|
275
|
-
"#{d.name}:#{spec.version.to_s}"
|
276
|
-
end
|
285
|
+
deps, dirs = dependencies_and_files_to_require_after_prune
|
277
286
|
|
278
287
|
log '* Pruning Bundler environment'
|
279
288
|
home = ENV['GEM_HOME']
|
280
289
|
Bundler.with_clean_env do
|
281
290
|
ENV['GEM_HOME'] = home
|
282
291
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
283
|
-
|
284
|
-
args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
292
|
+
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
285
293
|
# Ruby 2.0+ defaults to true which breaks socket activation
|
286
294
|
args += [{:close_others => false}]
|
287
295
|
Kernel.exec(*args)
|
288
296
|
end
|
289
297
|
end
|
290
298
|
|
299
|
+
def spec_for_gem(gem_name)
|
300
|
+
Bundler.rubygems.loaded_specs(gem_name)
|
301
|
+
end
|
302
|
+
|
303
|
+
def require_paths_for_gem(gem_spec)
|
304
|
+
gem_spec.full_require_paths
|
305
|
+
end
|
306
|
+
|
291
307
|
def log(str)
|
292
308
|
@events.log str
|
293
309
|
end
|
@@ -307,6 +323,21 @@ module Puma
|
|
307
323
|
log "- Goodbye!"
|
308
324
|
end
|
309
325
|
|
326
|
+
def log_thread_status
|
327
|
+
Thread.list.each do |thread|
|
328
|
+
log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
|
329
|
+
logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
|
330
|
+
logstr += " #{thread.name}" if thread.respond_to?(:name)
|
331
|
+
log logstr
|
332
|
+
|
333
|
+
if thread.backtrace
|
334
|
+
log thread.backtrace.join("\n")
|
335
|
+
else
|
336
|
+
log "<no backtrace available>"
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
310
341
|
def set_process_title
|
311
342
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
312
343
|
end
|
@@ -406,12 +437,6 @@ module Puma
|
|
406
437
|
|
407
438
|
begin
|
408
439
|
Signal.trap "SIGINT" do
|
409
|
-
if Puma.jruby?
|
410
|
-
@status = :exit
|
411
|
-
graceful_stop
|
412
|
-
exit
|
413
|
-
end
|
414
|
-
|
415
440
|
stop
|
416
441
|
end
|
417
442
|
rescue Exception
|
@@ -429,6 +454,22 @@ module Puma
|
|
429
454
|
rescue Exception
|
430
455
|
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
|
431
456
|
end
|
457
|
+
|
458
|
+
begin
|
459
|
+
Signal.trap "SIGINFO" do
|
460
|
+
log_thread_status
|
461
|
+
end
|
462
|
+
rescue Exception
|
463
|
+
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
|
464
|
+
# to see this constantly on Linux.
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def require_rubygems_min_version!(min_version, feature)
|
469
|
+
return if min_version <= Gem::Version.new(Gem::VERSION)
|
470
|
+
|
471
|
+
raise "#{feature} is not supported on your version of RubyGems. " \
|
472
|
+
"You must have RubyGems #{min_version}+ to use this feature."
|
432
473
|
end
|
433
474
|
end
|
434
475
|
end
|