puma 5.6.4 → 6.1.1
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 +199 -3
- data/README.md +22 -17
- data/bin/puma-wild +1 -1
- data/docs/compile_options.md +34 -0
- data/docs/fork_worker.md +1 -3
- data/docs/nginx.md +1 -1
- data/docs/systemd.md +1 -2
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/extconf.rb +18 -10
- data/ext/puma_http11/http11_parser.c +1 -1
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +2 -2
- data/ext/puma_http11/http11_parser.rl +2 -2
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +63 -24
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +166 -65
- data/ext/puma_http11/puma_http11.c +17 -9
- data/lib/puma/app/status.rb +6 -3
- data/lib/puma/binder.rb +41 -46
- data/lib/puma/cli.rb +11 -17
- data/lib/puma/client.rb +54 -16
- data/lib/puma/cluster/worker.rb +18 -11
- data/lib/puma/cluster/worker_handle.rb +4 -1
- data/lib/puma/cluster.rb +33 -30
- data/lib/puma/configuration.rb +75 -58
- data/lib/puma/const.rb +76 -88
- data/lib/puma/control_cli.rb +21 -18
- data/lib/puma/detect.rb +4 -0
- data/lib/puma/dsl.rb +111 -49
- data/lib/puma/error_logger.rb +17 -9
- data/lib/puma/events.rb +6 -126
- data/lib/puma/io_buffer.rb +39 -4
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +111 -175
- data/lib/puma/log_writer.rb +141 -0
- data/lib/puma/minissl/context_builder.rb +23 -12
- data/lib/puma/minissl.rb +91 -15
- data/lib/puma/null_io.rb +5 -0
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/rack/builder.rb +4 -4
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +4 -4
- data/lib/puma/request.rb +344 -161
- data/lib/puma/runner.rb +52 -20
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +57 -69
- data/lib/puma/single.rb +13 -11
- data/lib/puma/state_file.rb +2 -4
- data/lib/puma/thread_pool.rb +16 -16
- data/lib/puma/util.rb +12 -14
- data/lib/puma.rb +12 -11
- data/lib/rack/handler/puma.rb +115 -94
- metadata +10 -5
- data/lib/puma/queue_close.rb +0 -26
- data/lib/puma/systemd.rb +0 -46
data/lib/puma/jruby_restart.rb
CHANGED
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Puma
|
4
|
+
class Launcher
|
5
|
+
|
6
|
+
# This class is used to pickup Gemfile changes during
|
7
|
+
# application restarts.
|
8
|
+
class BundlePruner
|
9
|
+
|
10
|
+
def initialize(original_argv, extra_runtime_dependencies, log_writer)
|
11
|
+
@original_argv = Array(original_argv)
|
12
|
+
@extra_runtime_dependencies = Array(extra_runtime_dependencies)
|
13
|
+
@log_writer = log_writer
|
14
|
+
end
|
15
|
+
|
16
|
+
def prune
|
17
|
+
return if ENV['PUMA_BUNDLER_PRUNED']
|
18
|
+
return unless defined?(Bundler)
|
19
|
+
|
20
|
+
require_rubygems_min_version!
|
21
|
+
|
22
|
+
unless puma_wild_path
|
23
|
+
log "! Unable to prune Bundler environment, continuing"
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
dirs = paths_to_require_after_prune
|
28
|
+
|
29
|
+
log '* Pruning Bundler environment'
|
30
|
+
home = ENV['GEM_HOME']
|
31
|
+
bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
|
32
|
+
bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
|
33
|
+
|
34
|
+
with_unbundled_env do
|
35
|
+
ENV['GEM_HOME'] = home
|
36
|
+
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
37
|
+
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
38
|
+
ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
|
39
|
+
args = [Gem.ruby, puma_wild_path, '-I', dirs.join(':')] + @original_argv
|
40
|
+
# Ruby 2.0+ defaults to true which breaks socket activation
|
41
|
+
args += [{:close_others => false}]
|
42
|
+
Kernel.exec(*args)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def require_rubygems_min_version!
|
49
|
+
min_version = Gem::Version.new('2.2')
|
50
|
+
|
51
|
+
return if min_version <= Gem::Version.new(Gem::VERSION)
|
52
|
+
|
53
|
+
raise "prune_bundler is not supported on your version of RubyGems. " \
|
54
|
+
"You must have RubyGems #{min_version}+ to use this feature."
|
55
|
+
end
|
56
|
+
|
57
|
+
def puma_wild_path
|
58
|
+
puma_lib_dir = puma_require_paths.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
|
59
|
+
File.expand_path(File.join(puma_lib_dir, '../bin/puma-wild'))
|
60
|
+
end
|
61
|
+
|
62
|
+
def with_unbundled_env
|
63
|
+
bundler_ver = Gem::Version.new(Bundler::VERSION)
|
64
|
+
if bundler_ver < Gem::Version.new('2.1.0')
|
65
|
+
Bundler.with_clean_env { yield }
|
66
|
+
else
|
67
|
+
Bundler.with_unbundled_env { yield }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def paths_to_require_after_prune
|
72
|
+
puma_require_paths + extra_runtime_deps_paths
|
73
|
+
end
|
74
|
+
|
75
|
+
def extra_runtime_deps_paths
|
76
|
+
t = @extra_runtime_dependencies.map do |dep_name|
|
77
|
+
if (spec = spec_for_gem(dep_name))
|
78
|
+
require_paths_for_gem(spec)
|
79
|
+
else
|
80
|
+
log "* Could not load extra dependency: #{dep_name}"
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
t.flatten!; t.compact!; t
|
85
|
+
end
|
86
|
+
|
87
|
+
def puma_require_paths
|
88
|
+
require_paths_for_gem(spec_for_gem('puma'))
|
89
|
+
end
|
90
|
+
|
91
|
+
def spec_for_gem(gem_name)
|
92
|
+
Bundler.rubygems.loaded_specs(gem_name)
|
93
|
+
end
|
94
|
+
|
95
|
+
def require_paths_for_gem(gem_spec)
|
96
|
+
gem_spec.full_require_paths
|
97
|
+
end
|
98
|
+
|
99
|
+
def log(str)
|
100
|
+
@log_writer.log(str)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/puma/launcher.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
require_relative 'log_writer'
|
4
|
+
require_relative 'events'
|
5
|
+
require_relative 'detect'
|
6
|
+
require_relative 'cluster'
|
7
|
+
require_relative 'single'
|
8
|
+
require_relative 'const'
|
9
|
+
require_relative 'binder'
|
9
10
|
|
10
11
|
module Puma
|
11
12
|
# Puma::Launcher is the single entry point for starting a Puma server based on user
|
@@ -15,18 +16,14 @@ module Puma
|
|
15
16
|
# It is responsible for either launching a cluster of Puma workers or a single
|
16
17
|
# puma server.
|
17
18
|
class Launcher
|
18
|
-
|
19
|
-
|
20
|
-
:logger, :lowlevel_error_handler,
|
21
|
-
:before_worker_shutdown, :before_worker_boot, :before_worker_fork,
|
22
|
-
:after_worker_boot, :before_fork, :on_restart
|
23
|
-
]
|
19
|
+
autoload :BundlePruner, 'puma/launcher/bundle_pruner'
|
20
|
+
|
24
21
|
# Returns an instance of Launcher
|
25
22
|
#
|
26
23
|
# +conf+ A Puma::Configuration object indicating how to run the server.
|
27
24
|
#
|
28
25
|
# +launcher_args+ A Hash that currently has one required key `:events`,
|
29
|
-
# this is expected to hold an object similar to an `Puma::
|
26
|
+
# this is expected to hold an object similar to an `Puma::LogWriter.stdio`,
|
30
27
|
# this object will be responsible for broadcasting Puma's internal state
|
31
28
|
# to a logging destination. An optional key `:argv` can be supplied,
|
32
29
|
# this should be an array of strings, these arguments are re-used when
|
@@ -40,25 +37,35 @@ module Puma
|
|
40
37
|
# [200, {}, ["hello world"]]
|
41
38
|
# end
|
42
39
|
# end
|
43
|
-
# Puma::Launcher.new(conf,
|
40
|
+
# Puma::Launcher.new(conf, log_writer: Puma::LogWriter.stdio).run
|
44
41
|
def initialize(conf, launcher_args={})
|
45
42
|
@runner = nil
|
46
|
-
@
|
43
|
+
@log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
|
44
|
+
@events = launcher_args[:events] || Events.new
|
47
45
|
@argv = launcher_args[:argv] || []
|
48
46
|
@original_argv = @argv.dup
|
49
47
|
@config = conf
|
50
48
|
|
51
|
-
@
|
52
|
-
@binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
|
53
|
-
@binder.create_activated_fds(ENV).each { |k| ENV.delete k }
|
54
|
-
|
55
|
-
@environment = conf.environment
|
49
|
+
@config.options[:log_writer] = @log_writer
|
56
50
|
|
57
51
|
# Advertise the Configuration
|
58
52
|
Puma.cli_config = @config if defined?(Puma.cli_config)
|
59
53
|
|
60
54
|
@config.load
|
61
55
|
|
56
|
+
@binder = Binder.new(@log_writer, conf)
|
57
|
+
@binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
|
58
|
+
@binder.create_activated_fds(ENV).each { |k| ENV.delete k }
|
59
|
+
|
60
|
+
@environment = conf.environment
|
61
|
+
|
62
|
+
# Load the systemd integration if we detect systemd's NOTIFY_SOCKET.
|
63
|
+
# Skip this on JRuby though, because it is incompatible with the systemd
|
64
|
+
# integration due to https://github.com/jruby/jruby/issues/6504
|
65
|
+
if ENV["NOTIFY_SOCKET"] && !Puma.jruby?
|
66
|
+
@config.plugins.create('systemd')
|
67
|
+
end
|
68
|
+
|
62
69
|
if @config.options[:bind_to_activated_sockets]
|
63
70
|
@config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
|
64
71
|
@config.options[:binds],
|
@@ -69,8 +76,8 @@ module Puma
|
|
69
76
|
@options = @config.options
|
70
77
|
@config.clamp
|
71
78
|
|
72
|
-
@
|
73
|
-
@
|
79
|
+
@log_writer.formatter = LogWriter::PidFormatter.new if clustered?
|
80
|
+
@log_writer.formatter = options[:log_formatter] if @options[:log_formatter]
|
74
81
|
|
75
82
|
generate_restart_data
|
76
83
|
|
@@ -80,17 +87,17 @@ module Puma
|
|
80
87
|
|
81
88
|
Dir.chdir(@restart_dir)
|
82
89
|
|
83
|
-
prune_bundler
|
90
|
+
prune_bundler!
|
84
91
|
|
85
92
|
@environment = @options[:environment] if @options[:environment]
|
86
93
|
set_rack_environment
|
87
94
|
|
88
95
|
if clustered?
|
89
|
-
@options[:logger] = @
|
96
|
+
@options[:logger] = @log_writer
|
90
97
|
|
91
|
-
@runner = Cluster.new(self
|
98
|
+
@runner = Cluster.new(self)
|
92
99
|
else
|
93
|
-
@runner = Single.new(self
|
100
|
+
@runner = Single.new(self)
|
94
101
|
end
|
95
102
|
Puma.stats_object = @runner
|
96
103
|
|
@@ -99,7 +106,7 @@ module Puma
|
|
99
106
|
log_config if ENV['PUMA_LOG_CONFIG']
|
100
107
|
end
|
101
108
|
|
102
|
-
attr_reader :binder, :events, :config, :options, :restart_dir
|
109
|
+
attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir
|
103
110
|
|
104
111
|
# Return stats about the server
|
105
112
|
def stats
|
@@ -115,7 +122,7 @@ module Puma
|
|
115
122
|
permission = @options[:state_permission]
|
116
123
|
return unless path
|
117
124
|
|
118
|
-
|
125
|
+
require_relative 'state_file'
|
119
126
|
|
120
127
|
sf = StateFile.new
|
121
128
|
sf.pid = Process.pid
|
@@ -159,18 +166,20 @@ module Puma
|
|
159
166
|
true
|
160
167
|
end
|
161
168
|
|
169
|
+
# Begin a refork if supported
|
170
|
+
def refork
|
171
|
+
if clustered? && @runner.respond_to?(:fork_worker!) && @options[:fork_worker]
|
172
|
+
@runner.fork_worker!
|
173
|
+
true
|
174
|
+
else
|
175
|
+
log "* refork called but not available."
|
176
|
+
false
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
162
180
|
# Run the server. This blocks until the server is stopped
|
163
181
|
def run
|
164
|
-
previous_env =
|
165
|
-
if defined?(Bundler)
|
166
|
-
env = Bundler::ORIGINAL_ENV.dup
|
167
|
-
# add -rbundler/setup so we load from Gemfile when restarting
|
168
|
-
bundle = "-rbundler/setup"
|
169
|
-
env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
|
170
|
-
env
|
171
|
-
else
|
172
|
-
ENV.to_h
|
173
|
-
end
|
182
|
+
previous_env = get_env
|
174
183
|
|
175
184
|
@config.clamp
|
176
185
|
|
@@ -178,24 +187,11 @@ module Puma
|
|
178
187
|
|
179
188
|
setup_signals
|
180
189
|
set_process_title
|
181
|
-
|
190
|
+
|
191
|
+
# This blocks until the server is stopped
|
182
192
|
@runner.run
|
183
193
|
|
184
|
-
|
185
|
-
when :halt
|
186
|
-
log "* Stopping immediately!"
|
187
|
-
@runner.stop_control
|
188
|
-
when :run, :stop
|
189
|
-
graceful_stop
|
190
|
-
when :restart
|
191
|
-
log "* Restarting..."
|
192
|
-
ENV.replace(previous_env)
|
193
|
-
@runner.stop_control
|
194
|
-
restart!
|
195
|
-
when :exit
|
196
|
-
# nothing
|
197
|
-
end
|
198
|
-
close_binder_listeners unless @status == :restart
|
194
|
+
do_run_finished(previous_env)
|
199
195
|
end
|
200
196
|
|
201
197
|
# Return all tcp ports the launcher may be using, TCP or SSL
|
@@ -239,30 +235,56 @@ module Puma
|
|
239
235
|
|
240
236
|
private
|
241
237
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
238
|
+
def get_env
|
239
|
+
if defined?(Bundler)
|
240
|
+
env = Bundler::ORIGINAL_ENV.dup
|
241
|
+
# add -rbundler/setup so we load from Gemfile when restarting
|
242
|
+
bundle = "-rbundler/setup"
|
243
|
+
env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
|
244
|
+
env
|
245
|
+
else
|
246
|
+
ENV.to_h
|
251
247
|
end
|
252
248
|
end
|
253
249
|
|
254
|
-
def
|
255
|
-
|
250
|
+
def do_run_finished(previous_env)
|
251
|
+
case @status
|
252
|
+
when :halt
|
253
|
+
do_forceful_stop
|
254
|
+
when :run, :stop
|
255
|
+
do_graceful_stop
|
256
|
+
when :restart
|
257
|
+
do_restart(previous_env)
|
258
|
+
end
|
259
|
+
|
260
|
+
close_binder_listeners unless @status == :restart
|
261
|
+
end
|
262
|
+
|
263
|
+
def do_forceful_stop
|
264
|
+
log "* Stopping immediately!"
|
265
|
+
@runner.stop_control
|
266
|
+
end
|
267
|
+
|
268
|
+
def do_graceful_stop
|
269
|
+
@events.fire_on_stopped!
|
270
|
+
@runner.stop_blocked
|
271
|
+
end
|
272
|
+
|
273
|
+
def do_restart(previous_env)
|
274
|
+
log "* Restarting..."
|
275
|
+
ENV.replace(previous_env)
|
276
|
+
@runner.stop_control
|
277
|
+
restart!
|
256
278
|
end
|
257
279
|
|
258
280
|
def restart!
|
259
281
|
@events.fire_on_restart!
|
260
|
-
@config.run_hooks :on_restart, self, @
|
282
|
+
@config.run_hooks :on_restart, self, @log_writer
|
261
283
|
|
262
284
|
if Puma.jruby?
|
263
285
|
close_binder_listeners
|
264
286
|
|
265
|
-
|
287
|
+
require_relative 'jruby_restart'
|
266
288
|
JRubyRestart.chdir_exec(@restart_dir, restart_args)
|
267
289
|
elsif Puma.windows?
|
268
290
|
close_binder_listeners
|
@@ -279,94 +301,24 @@ module Puma
|
|
279
301
|
end
|
280
302
|
end
|
281
303
|
|
282
|
-
#
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
Array(@options[:extra_runtime_dependencies]).map do |d_name|
|
292
|
-
if (spec = spec_for_gem(d_name))
|
293
|
-
require_paths_for_gem(spec)
|
294
|
-
else
|
295
|
-
log "* Could not load extra dependency: #{d_name}"
|
296
|
-
nil
|
297
|
-
end
|
298
|
-
end.flatten.compact
|
299
|
-
end
|
300
|
-
|
301
|
-
# @!attribute [r] puma_wild_location
|
302
|
-
def puma_wild_location
|
303
|
-
puma = spec_for_gem("puma")
|
304
|
-
dirs = require_paths_for_gem(puma)
|
305
|
-
puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
|
306
|
-
File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
|
307
|
-
end
|
308
|
-
|
309
|
-
def prune_bundler
|
310
|
-
return if ENV['PUMA_BUNDLER_PRUNED']
|
311
|
-
return unless defined?(Bundler)
|
312
|
-
require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
|
313
|
-
unless puma_wild_location
|
314
|
-
log "! Unable to prune Bundler environment, continuing"
|
315
|
-
return
|
316
|
-
end
|
317
|
-
|
318
|
-
dirs = files_to_require_after_prune
|
319
|
-
|
320
|
-
log '* Pruning Bundler environment'
|
321
|
-
home = ENV['GEM_HOME']
|
322
|
-
bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
|
323
|
-
bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
|
324
|
-
with_unbundled_env do
|
325
|
-
ENV['GEM_HOME'] = home
|
326
|
-
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
327
|
-
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
328
|
-
ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
|
329
|
-
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')] + @original_argv
|
330
|
-
# Ruby 2.0+ defaults to true which breaks socket activation
|
331
|
-
args += [{:close_others => false}]
|
332
|
-
Kernel.exec(*args)
|
333
|
-
end
|
334
|
-
end
|
335
|
-
|
336
|
-
#
|
337
|
-
# Puma's systemd integration allows Puma to inform systemd:
|
338
|
-
# 1. when it has successfully started
|
339
|
-
# 2. when it is starting shutdown
|
340
|
-
# 3. periodically for a liveness check with a watchdog thread
|
341
|
-
#
|
342
|
-
|
343
|
-
def integrate_with_systemd
|
344
|
-
return unless ENV["NOTIFY_SOCKET"]
|
345
|
-
|
346
|
-
begin
|
347
|
-
require 'puma/systemd'
|
348
|
-
rescue LoadError
|
349
|
-
log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
|
350
|
-
return
|
304
|
+
# If configured, write the pid of the current process out
|
305
|
+
# to a file.
|
306
|
+
def write_pid
|
307
|
+
path = @options[:pidfile]
|
308
|
+
return unless path
|
309
|
+
cur_pid = Process.pid
|
310
|
+
File.write path, cur_pid, mode: 'wb:UTF-8'
|
311
|
+
at_exit do
|
312
|
+
delete_pidfile if cur_pid == Process.pid
|
351
313
|
end
|
352
|
-
|
353
|
-
log "* Enabling systemd notification integration"
|
354
|
-
|
355
|
-
systemd = Systemd.new(@events)
|
356
|
-
systemd.hook_events
|
357
|
-
systemd.start_watchdog
|
358
314
|
end
|
359
315
|
|
360
|
-
def
|
361
|
-
|
362
|
-
end
|
363
|
-
|
364
|
-
def require_paths_for_gem(gem_spec)
|
365
|
-
gem_spec.full_require_paths
|
316
|
+
def reload_worker_directory
|
317
|
+
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
366
318
|
end
|
367
319
|
|
368
320
|
def log(str)
|
369
|
-
@
|
321
|
+
@log_writer.log(str)
|
370
322
|
end
|
371
323
|
|
372
324
|
def clustered?
|
@@ -374,15 +326,10 @@ module Puma
|
|
374
326
|
end
|
375
327
|
|
376
328
|
def unsupported(str)
|
377
|
-
@
|
329
|
+
@log_writer.error(str)
|
378
330
|
raise UnsupportedOption
|
379
331
|
end
|
380
332
|
|
381
|
-
def graceful_stop
|
382
|
-
@events.fire_on_stopped!
|
383
|
-
@runner.stop_blocked
|
384
|
-
end
|
385
|
-
|
386
333
|
def set_process_title
|
387
334
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
388
335
|
end
|
@@ -408,6 +355,11 @@ module Puma
|
|
408
355
|
@options[:prune_bundler] && clustered? && !@options[:preload_app]
|
409
356
|
end
|
410
357
|
|
358
|
+
def prune_bundler!
|
359
|
+
return unless prune_bundler?
|
360
|
+
BundlePruner.new(@original_argv, @options[:extra_runtime_dependencies], @log_writer).prune
|
361
|
+
end
|
362
|
+
|
411
363
|
def generate_restart_data
|
412
364
|
if dir = @options[:directory]
|
413
365
|
@restart_dir = dir
|
@@ -474,7 +426,8 @@ module Puma
|
|
474
426
|
|
475
427
|
begin
|
476
428
|
Signal.trap "SIGTERM" do
|
477
|
-
|
429
|
+
# Shortcut the control flow in case raise_exception_on_sigterm is true
|
430
|
+
do_graceful_stop
|
478
431
|
|
479
432
|
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
480
433
|
end
|
@@ -506,8 +459,8 @@ module Puma
|
|
506
459
|
unless Puma.jruby? # INFO in use by JVM already
|
507
460
|
Signal.trap "SIGINFO" do
|
508
461
|
thread_status do |name, backtrace|
|
509
|
-
@
|
510
|
-
@
|
462
|
+
@log_writer.log(name)
|
463
|
+
@log_writer.log(backtrace.map { |bt| " #{bt}" })
|
511
464
|
end
|
512
465
|
end
|
513
466
|
end
|
@@ -517,23 +470,6 @@ module Puma
|
|
517
470
|
end
|
518
471
|
end
|
519
472
|
|
520
|
-
def require_rubygems_min_version!(min_version, feature)
|
521
|
-
return if min_version <= Gem::Version.new(Gem::VERSION)
|
522
|
-
|
523
|
-
raise "#{feature} is not supported on your version of RubyGems. " \
|
524
|
-
"You must have RubyGems #{min_version}+ to use this feature."
|
525
|
-
end
|
526
|
-
|
527
|
-
# @version 5.0.0
|
528
|
-
def with_unbundled_env
|
529
|
-
bundler_ver = Gem::Version.new(Bundler::VERSION)
|
530
|
-
if bundler_ver < Gem::Version.new('2.1.0')
|
531
|
-
Bundler.with_clean_env { yield }
|
532
|
-
else
|
533
|
-
Bundler.with_unbundled_env { yield }
|
534
|
-
end
|
535
|
-
end
|
536
|
-
|
537
473
|
def log_config
|
538
474
|
log "Configuration:"
|
539
475
|
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'null_io'
|
4
|
+
require_relative 'error_logger'
|
5
|
+
require 'stringio'
|
6
|
+
require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
|
7
|
+
|
8
|
+
module Puma
|
9
|
+
|
10
|
+
# Handles logging concerns for both standard messages
|
11
|
+
# (+stdout+) and errors (+stderr+).
|
12
|
+
class LogWriter
|
13
|
+
|
14
|
+
class DefaultFormatter
|
15
|
+
def call(str)
|
16
|
+
str
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class PidFormatter
|
21
|
+
def call(str)
|
22
|
+
"[#{$$}] #{str}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
LOG_QUEUE = Queue.new
|
27
|
+
|
28
|
+
attr_reader :stdout,
|
29
|
+
:stderr
|
30
|
+
|
31
|
+
attr_accessor :formatter
|
32
|
+
|
33
|
+
# Create a LogWriter that prints to +stdout+ and +stderr+.
|
34
|
+
def initialize(stdout, stderr)
|
35
|
+
@formatter = DefaultFormatter.new
|
36
|
+
@stdout = stdout
|
37
|
+
@stderr = stderr
|
38
|
+
|
39
|
+
@debug = ENV.key?('PUMA_DEBUG')
|
40
|
+
@error_logger = ErrorLogger.new(@stderr)
|
41
|
+
end
|
42
|
+
|
43
|
+
DEFAULT = new(STDOUT, STDERR)
|
44
|
+
|
45
|
+
# Returns an LogWriter object which writes its status to
|
46
|
+
# two StringIO objects.
|
47
|
+
def self.strings
|
48
|
+
LogWriter.new(StringIO.new, StringIO.new)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.stdio
|
52
|
+
LogWriter.new($stdout, $stderr)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.null
|
56
|
+
n = NullIO.new
|
57
|
+
LogWriter.new(n, n)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Write +str+ to +@stdout+
|
61
|
+
def log(str)
|
62
|
+
internal_write "#{@formatter.call str}\n"
|
63
|
+
end
|
64
|
+
|
65
|
+
def write(str)
|
66
|
+
internal_write @formatter.call(str)
|
67
|
+
end
|
68
|
+
|
69
|
+
def internal_write(str)
|
70
|
+
LOG_QUEUE << str
|
71
|
+
while (w_str = LOG_QUEUE.pop(true)) do
|
72
|
+
begin
|
73
|
+
@stdout.is_a?(IO) and @stdout.wait_writable(1)
|
74
|
+
@stdout.write w_str
|
75
|
+
@stdout.flush unless @stdout.sync
|
76
|
+
rescue Errno::EPIPE, Errno::EBADF, IOError
|
77
|
+
end
|
78
|
+
end
|
79
|
+
rescue ThreadError
|
80
|
+
end
|
81
|
+
private :internal_write
|
82
|
+
|
83
|
+
def debug?
|
84
|
+
@debug
|
85
|
+
end
|
86
|
+
|
87
|
+
def debug(str)
|
88
|
+
log("% #{str}") if @debug
|
89
|
+
end
|
90
|
+
|
91
|
+
# Write +str+ to +@stderr+
|
92
|
+
def error(str)
|
93
|
+
@error_logger.info(text: @formatter.call("ERROR: #{str}"))
|
94
|
+
exit 1
|
95
|
+
end
|
96
|
+
|
97
|
+
def format(str)
|
98
|
+
formatter.call(str)
|
99
|
+
end
|
100
|
+
|
101
|
+
# An HTTP connection error has occurred.
|
102
|
+
# +error+ a connection exception, +req+ the request,
|
103
|
+
# and +text+ additional info
|
104
|
+
# @version 5.0.0
|
105
|
+
def connection_error(error, req, text="HTTP connection error")
|
106
|
+
@error_logger.info(error: error, req: req, text: text)
|
107
|
+
end
|
108
|
+
|
109
|
+
# An HTTP parse error has occurred.
|
110
|
+
# +error+ a parsing exception,
|
111
|
+
# and +req+ the request.
|
112
|
+
def parse_error(error, req)
|
113
|
+
@error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
|
114
|
+
end
|
115
|
+
|
116
|
+
# An SSL error has occurred.
|
117
|
+
# @param error <Puma::MiniSSL::SSLError>
|
118
|
+
# @param ssl_socket <Puma::MiniSSL::Socket>
|
119
|
+
def ssl_error(error, ssl_socket)
|
120
|
+
peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
|
121
|
+
peercert = ssl_socket.peercert
|
122
|
+
subject = peercert ? peercert.subject : nil
|
123
|
+
@error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
|
124
|
+
end
|
125
|
+
|
126
|
+
# An unknown error has occurred.
|
127
|
+
# +error+ an exception object, +req+ the request,
|
128
|
+
# and +text+ additional info
|
129
|
+
def unknown_error(error, req=nil, text="Unknown error")
|
130
|
+
@error_logger.info(error: error, req: req, text: text)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Log occurred error debug dump.
|
134
|
+
# +error+ an exception object, +req+ the request,
|
135
|
+
# and +text+ additional info
|
136
|
+
# @version 5.0.0
|
137
|
+
def debug_error(error, req=nil, text="")
|
138
|
+
@error_logger.debug(error: error, req: req, text: text)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|