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