puma 3.11.4 → 4.2.0
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 +130 -1
- data/README.md +100 -44
- data/docs/architecture.md +1 -0
- data/docs/deployment.md +24 -4
- data/docs/restart.md +4 -2
- data/docs/systemd.md +27 -9
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/extconf.rb +8 -0
- data/ext/puma_http11/http11_parser.c +37 -62
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +96 -5
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +21 -4
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +35 -29
- data/lib/puma/binder.rb +47 -11
- data/lib/puma/cli.rb +21 -7
- data/lib/puma/client.rb +227 -191
- data/lib/puma/cluster.rb +70 -31
- data/lib/puma/commonlogger.rb +2 -0
- data/lib/puma/configuration.rb +6 -3
- data/lib/puma/const.rb +24 -18
- data/lib/puma/control_cli.rb +33 -14
- data/lib/puma/convenient.rb +2 -0
- data/lib/puma/delegation.rb +2 -0
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +308 -76
- data/lib/puma/events.rb +6 -1
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -0
- data/lib/puma/launcher.rb +102 -55
- data/lib/puma/minissl.rb +41 -19
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/plugin.rb +7 -2
- data/lib/puma/rack/builder.rb +4 -1
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +220 -34
- data/lib/puma/runner.rb +14 -4
- data/lib/puma/server.rb +82 -40
- data/lib/puma/single.rb +15 -3
- data/lib/puma/state_file.rb +2 -0
- data/lib/puma/tcp_logger.rb +2 -0
- data/lib/puma/thread_pool.rb +59 -36
- data/lib/puma/util.rb +2 -6
- data/lib/puma.rb +8 -0
- data/lib/rack/handler/puma.rb +6 -3
- data/tools/docker/Dockerfile +16 -0
- data/tools/jungle/init.d/puma +6 -6
- data/tools/trickletest.rb +0 -1
- metadata +22 -10
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
data/lib/puma/launcher.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/events'
|
2
4
|
require 'puma/detect'
|
3
|
-
|
4
5
|
require 'puma/cluster'
|
5
6
|
require 'puma/single'
|
6
|
-
|
7
7
|
require 'puma/const'
|
8
|
-
|
9
8
|
require 'puma/binder'
|
10
9
|
|
11
10
|
module Puma
|
@@ -61,10 +60,13 @@ module Puma
|
|
61
60
|
@options = @config.options
|
62
61
|
@config.clamp
|
63
62
|
|
63
|
+
@events.formatter = Events::PidFormatter.new if clustered?
|
64
|
+
@events.formatter = options[:log_formatter] if @options[:log_formatter]
|
65
|
+
|
64
66
|
generate_restart_data
|
65
67
|
|
66
|
-
if clustered? &&
|
67
|
-
unsupported
|
68
|
+
if clustered? && !Process.respond_to?(:fork)
|
69
|
+
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
|
68
70
|
end
|
69
71
|
|
70
72
|
if @options[:daemon] && Puma.windows?
|
@@ -79,7 +81,6 @@ module Puma
|
|
79
81
|
set_rack_environment
|
80
82
|
|
81
83
|
if clustered?
|
82
|
-
@events.formatter = Events::PidFormatter.new
|
83
84
|
@options[:logger] = @events
|
84
85
|
|
85
86
|
@runner = Cluster.new(self, @events)
|
@@ -122,19 +123,6 @@ module Puma
|
|
122
123
|
File.unlink(path) if path && File.exist?(path)
|
123
124
|
end
|
124
125
|
|
125
|
-
# If configured, write the pid of the current process out
|
126
|
-
# to a file.
|
127
|
-
def write_pid
|
128
|
-
path = @options[:pidfile]
|
129
|
-
return unless path
|
130
|
-
|
131
|
-
File.open(path, 'w') { |f| f.puts Process.pid }
|
132
|
-
cur = Process.pid
|
133
|
-
at_exit do
|
134
|
-
delete_pidfile if cur == Process.pid
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
126
|
# Begin async shutdown of the server
|
139
127
|
def halt
|
140
128
|
@status = :halt
|
@@ -212,8 +200,29 @@ module Puma
|
|
212
200
|
end
|
213
201
|
end
|
214
202
|
|
203
|
+
def close_binder_listeners
|
204
|
+
@binder.close_listeners
|
205
|
+
end
|
206
|
+
|
207
|
+
def close_binder_unix_paths
|
208
|
+
@binder.close_unix_paths
|
209
|
+
end
|
210
|
+
|
215
211
|
private
|
216
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
|
+
|
217
226
|
def reload_worker_directory
|
218
227
|
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
219
228
|
end
|
@@ -233,48 +242,71 @@ module Puma
|
|
233
242
|
Dir.chdir(@restart_dir)
|
234
243
|
Kernel.exec(*argv)
|
235
244
|
else
|
236
|
-
redirects = {:close_others => true}
|
237
|
-
@binder.listeners.each_with_index do |(l, io), i|
|
238
|
-
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
239
|
-
redirects[io.to_i] = io.to_i
|
240
|
-
end
|
241
|
-
|
242
245
|
argv = restart_args
|
243
246
|
Dir.chdir(@restart_dir)
|
244
|
-
argv += [
|
247
|
+
argv += [@binder.redirects_for_restart]
|
245
248
|
Kernel.exec(*argv)
|
246
249
|
end
|
247
250
|
end
|
248
251
|
|
249
|
-
def
|
250
|
-
|
251
|
-
|
252
|
-
|
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)
|
253
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
|
254
279
|
|
255
|
-
|
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
|
256
284
|
log "! Unable to prune Bundler environment, continuing"
|
257
285
|
return
|
258
286
|
end
|
259
287
|
|
260
|
-
deps =
|
261
|
-
spec = Bundler.rubygems.loaded_specs(d.name)
|
262
|
-
"#{d.name}:#{spec.version.to_s}"
|
263
|
-
end
|
288
|
+
deps, dirs = dependencies_and_files_to_require_after_prune
|
264
289
|
|
265
290
|
log '* Pruning Bundler environment'
|
266
291
|
home = ENV['GEM_HOME']
|
267
292
|
Bundler.with_clean_env do
|
268
293
|
ENV['GEM_HOME'] = home
|
269
294
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
270
|
-
|
271
|
-
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
|
272
296
|
# Ruby 2.0+ defaults to true which breaks socket activation
|
273
|
-
args += [{:close_others => false}]
|
297
|
+
args += [{:close_others => false}]
|
274
298
|
Kernel.exec(*args)
|
275
299
|
end
|
276
300
|
end
|
277
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
|
+
|
278
310
|
def log(str)
|
279
311
|
@events.log str
|
280
312
|
end
|
@@ -294,6 +326,21 @@ module Puma
|
|
294
326
|
log "- Goodbye!"
|
295
327
|
end
|
296
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
|
+
|
297
344
|
def set_process_title
|
298
345
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
299
346
|
end
|
@@ -317,16 +364,6 @@ module Puma
|
|
317
364
|
@options[:prune_bundler] && clustered? && !@options[:preload_app]
|
318
365
|
end
|
319
366
|
|
320
|
-
def close_binder_listeners
|
321
|
-
@binder.listeners.each do |l, io|
|
322
|
-
io.close
|
323
|
-
uri = URI.parse(l)
|
324
|
-
next unless uri.scheme == 'unix'
|
325
|
-
File.unlink("#{uri.host}#{uri.path}")
|
326
|
-
end
|
327
|
-
end
|
328
|
-
|
329
|
-
|
330
367
|
def generate_restart_data
|
331
368
|
if dir = @options[:directory]
|
332
369
|
@restart_dir = dir
|
@@ -395,7 +432,7 @@ module Puma
|
|
395
432
|
Signal.trap "SIGTERM" do
|
396
433
|
graceful_stop
|
397
434
|
|
398
|
-
raise
|
435
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
399
436
|
end
|
400
437
|
rescue Exception
|
401
438
|
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
|
@@ -403,12 +440,6 @@ module Puma
|
|
403
440
|
|
404
441
|
begin
|
405
442
|
Signal.trap "SIGINT" do
|
406
|
-
if Puma.jruby?
|
407
|
-
@status = :exit
|
408
|
-
graceful_stop
|
409
|
-
exit
|
410
|
-
end
|
411
|
-
|
412
443
|
stop
|
413
444
|
end
|
414
445
|
rescue Exception
|
@@ -426,6 +457,22 @@ module Puma
|
|
426
457
|
rescue Exception
|
427
458
|
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
|
428
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."
|
429
476
|
end
|
430
477
|
end
|
431
478
|
end
|
data/lib/puma/minissl.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
begin
|
2
4
|
require 'io/wait'
|
3
|
-
|
5
|
+
rescue LoadError
|
4
6
|
end
|
5
7
|
|
6
8
|
module Puma
|
@@ -52,22 +54,21 @@ module Puma
|
|
52
54
|
output = engine_read_all
|
53
55
|
return output if output
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end while true
|
57
|
+
data = @socket.read_nonblock(size, exception: false)
|
58
|
+
if data == :wait_readable || data == :wait_writable
|
59
|
+
# It would make more sense to let @socket.read_nonblock raise
|
60
|
+
# EAGAIN if necessary but it seems like it'll misbehave on Windows.
|
61
|
+
# I don't have a Windows machine to debug this so I can't explain
|
62
|
+
# exactly whats happening in that OS. Please let me know if you
|
63
|
+
# find out!
|
64
|
+
#
|
65
|
+
# In the meantime, we can emulate the correct behavior by
|
66
|
+
# capturing :wait_readable & :wait_writable and raising EAGAIN
|
67
|
+
# ourselves.
|
68
|
+
raise IO::EAGAINWaitReadable
|
69
|
+
elsif data.nil?
|
70
|
+
return nil
|
71
|
+
end
|
71
72
|
|
72
73
|
@engine.inject(data)
|
73
74
|
output = engine_read_all
|
@@ -124,7 +125,7 @@ module Puma
|
|
124
125
|
|
125
126
|
def read_and_drop(timeout = 1)
|
126
127
|
return :timeout unless IO.select([@socket], nil, nil, timeout)
|
127
|
-
read_nonblock(1024)
|
128
|
+
return :eof unless read_nonblock(1024)
|
128
129
|
:drop
|
129
130
|
rescue Errno::EAGAIN
|
130
131
|
# do nothing
|
@@ -141,7 +142,7 @@ module Puma
|
|
141
142
|
# Don't let this socket hold this loop forever.
|
142
143
|
# If it can't send more packets within 1s, then give up.
|
143
144
|
while should_drop_bytes?
|
144
|
-
return if read_and_drop(1)
|
145
|
+
return if [:timeout, :eof].include?(read_and_drop(1))
|
145
146
|
end
|
146
147
|
rescue IOError, SystemCallError
|
147
148
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
@@ -175,11 +176,18 @@ module Puma
|
|
175
176
|
|
176
177
|
class Context
|
177
178
|
attr_accessor :verify_mode
|
179
|
+
attr_reader :no_tlsv1, :no_tlsv1_1
|
180
|
+
|
181
|
+
def initialize
|
182
|
+
@no_tlsv1 = false
|
183
|
+
@no_tlsv1_1 = false
|
184
|
+
end
|
178
185
|
|
179
186
|
if defined?(JRUBY_VERSION)
|
180
187
|
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
181
188
|
attr_reader :keystore
|
182
189
|
attr_accessor :keystore_pass
|
190
|
+
attr_accessor :ssl_cipher_list
|
183
191
|
|
184
192
|
def keystore=(keystore)
|
185
193
|
raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
|
@@ -195,6 +203,7 @@ module Puma
|
|
195
203
|
attr_reader :key
|
196
204
|
attr_reader :cert
|
197
205
|
attr_reader :ca
|
206
|
+
attr_accessor :ssl_cipher_filter
|
198
207
|
|
199
208
|
def key=(key)
|
200
209
|
raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
|
@@ -216,6 +225,19 @@ module Puma
|
|
216
225
|
raise "Cert not configured" unless @cert
|
217
226
|
end
|
218
227
|
end
|
228
|
+
|
229
|
+
# disables TLSv1
|
230
|
+
def no_tlsv1=(tlsv1)
|
231
|
+
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
|
232
|
+
@no_tlsv1 = tlsv1
|
233
|
+
end
|
234
|
+
|
235
|
+
# disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
|
236
|
+
def no_tlsv1_1=(tlsv1_1)
|
237
|
+
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
|
238
|
+
@no_tlsv1_1 = tlsv1_1
|
239
|
+
end
|
240
|
+
|
219
241
|
end
|
220
242
|
|
221
243
|
VERIFY_NONE = 0
|
data/lib/puma/null_io.rb
CHANGED
data/lib/puma/plugin.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Puma
|
2
4
|
class UnknownPlugin < RuntimeError; end
|
3
5
|
|
@@ -60,8 +62,11 @@ module Puma
|
|
60
62
|
end
|
61
63
|
|
62
64
|
def fire_background
|
63
|
-
@background.
|
64
|
-
Thread.new
|
65
|
+
@background.each_with_index do |b, i|
|
66
|
+
Thread.new do
|
67
|
+
Puma.set_thread_name "plugin background #{i}"
|
68
|
+
b.call
|
69
|
+
end
|
65
70
|
end
|
66
71
|
end
|
67
72
|
end
|
data/lib/puma/rack/builder.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Puma
|
2
4
|
end
|
3
5
|
|
@@ -110,7 +112,8 @@ module Puma::Rack
|
|
110
112
|
|
111
113
|
has_options = false
|
112
114
|
server.valid_options.each do |name, description|
|
113
|
-
next if name.to_s
|
115
|
+
next if name.to_s =~ /^(Host|Port)[^a-zA-Z]/ # ignore handler's host and port options, we do our own.
|
116
|
+
|
114
117
|
info << " -O %-21s %s" % [name, description]
|
115
118
|
has_options = true
|
116
119
|
end
|
data/lib/puma/rack/urlmap.rb
CHANGED