puma 4.3.12 → 6.3.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 +1729 -521
- data/LICENSE +23 -20
- data/README.md +169 -45
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +63 -26
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +60 -69
- data/docs/fork_worker.md +31 -0
- 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 +9 -0
- data/{tools → docs}/jungle/rc.d/README.md +1 -1
- data/{tools → docs}/jungle/rc.d/puma +2 -2
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +2 -2
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +46 -23
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +84 -128
- 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 +2 -4
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +49 -12
- data/ext/puma_http11/http11_parser.c +46 -48
- data/ext/puma_http11/http11_parser.h +2 -2
- data/ext/puma_http11/http11_parser.java.rl +3 -3
- data/ext/puma_http11/http11_parser.rl +3 -3
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +278 -93
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
- data/ext/puma_http11/puma_http11.c +46 -57
- data/lib/puma/app/status.rb +53 -39
- data/lib/puma/binder.rb +237 -121
- data/lib/puma/cli.rb +34 -34
- data/lib/puma/client.rb +172 -98
- data/lib/puma/cluster/worker.rb +180 -0
- data/lib/puma/cluster/worker_handle.rb +97 -0
- data/lib/puma/cluster.rb +226 -231
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +114 -87
- data/lib/puma/const.rb +139 -95
- data/lib/puma/control_cli.rb +99 -79
- data/lib/puma/detect.rb +33 -2
- data/lib/puma/dsl.rb +516 -110
- data/lib/puma/error_logger.rb +113 -0
- data/lib/puma/events.rb +16 -115
- data/lib/puma/io_buffer.rb +44 -2
- data/lib/puma/jruby_restart.rb +2 -59
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +164 -155
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +36 -19
- data/lib/puma/minissl.rb +230 -55
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +3 -12
- data/lib/puma/rack/builder.rb +7 -11
- data/lib/puma/rack/urlmap.rb +0 -0
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +93 -368
- data/lib/puma/request.rb +671 -0
- data/lib/puma/runner.rb +92 -75
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +321 -794
- data/lib/puma/single.rb +20 -74
- data/lib/puma/state_file.rb +45 -8
- data/lib/puma/thread_pool.rb +140 -68
- data/lib/puma/util.rb +21 -4
- data/lib/puma.rb +54 -7
- data/lib/rack/handler/puma.rb +113 -87
- data/tools/{docker/Dockerfile → Dockerfile} +1 -1
- data/tools/trickletest.rb +0 -0
- metadata +33 -24
- data/docs/tcp_mode.md +0 -96
- data/ext/puma_http11/io_buffer.c +0 -155
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
- data/lib/puma/accept_nonblock.rb +0 -29
- data/lib/puma/tcp_logger.rb +0 -41
- data/tools/jungle/README.md +0 -19
- data/tools/jungle/init.d/README.md +0 -61
- data/tools/jungle/init.d/puma +0 -421
- data/tools/jungle/init.d/run-puma +0 -18
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- data/tools/jungle/upstart/puma.conf +0 -69
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,60 +37,78 @@ 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.import_from_env
|
52
|
-
|
53
|
-
@environment = conf.environment
|
49
|
+
@config.options[:log_writer] = @log_writer
|
54
50
|
|
55
51
|
# Advertise the Configuration
|
56
52
|
Puma.cli_config = @config if defined?(Puma.cli_config)
|
57
53
|
|
58
54
|
@config.load
|
59
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
|
+
|
69
|
+
if @config.options[:bind_to_activated_sockets]
|
70
|
+
@config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
|
71
|
+
@config.options[:binds],
|
72
|
+
@config.options[:bind_to_activated_sockets] == 'only'
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
60
76
|
@options = @config.options
|
61
77
|
@config.clamp
|
62
78
|
|
63
|
-
@
|
64
|
-
@
|
79
|
+
@log_writer.formatter = LogWriter::PidFormatter.new if clustered?
|
80
|
+
@log_writer.formatter = options[:log_formatter] if @options[:log_formatter]
|
81
|
+
|
82
|
+
@log_writer.custom_logger = options[:custom_logger] if @options[:custom_logger]
|
65
83
|
|
66
84
|
generate_restart_data
|
67
85
|
|
68
|
-
if clustered? && !
|
86
|
+
if clustered? && !Puma.forkable?
|
69
87
|
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
|
70
88
|
end
|
71
89
|
|
72
|
-
if @options[:daemon] && Puma.windows?
|
73
|
-
unsupported 'daemon mode not supported on Windows'
|
74
|
-
end
|
75
|
-
|
76
90
|
Dir.chdir(@restart_dir)
|
77
91
|
|
78
|
-
prune_bundler
|
92
|
+
prune_bundler!
|
79
93
|
|
80
94
|
@environment = @options[:environment] if @options[:environment]
|
81
95
|
set_rack_environment
|
82
96
|
|
83
97
|
if clustered?
|
84
|
-
@options[:logger] = @
|
98
|
+
@options[:logger] = @log_writer
|
85
99
|
|
86
|
-
@runner = Cluster.new(self
|
100
|
+
@runner = Cluster.new(self)
|
87
101
|
else
|
88
|
-
@runner = Single.new(self
|
102
|
+
@runner = Single.new(self)
|
89
103
|
end
|
90
104
|
Puma.stats_object = @runner
|
91
105
|
|
92
106
|
@status = :run
|
107
|
+
|
108
|
+
log_config if ENV['PUMA_LOG_CONFIG']
|
93
109
|
end
|
94
110
|
|
95
|
-
attr_reader :binder, :events, :config, :options, :restart_dir
|
111
|
+
attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir
|
96
112
|
|
97
113
|
# Return stats about the server
|
98
114
|
def stats
|
@@ -105,16 +121,18 @@ module Puma
|
|
105
121
|
write_pid
|
106
122
|
|
107
123
|
path = @options[:state]
|
124
|
+
permission = @options[:state_permission]
|
108
125
|
return unless path
|
109
126
|
|
110
|
-
|
127
|
+
require_relative 'state_file'
|
111
128
|
|
112
129
|
sf = StateFile.new
|
113
130
|
sf.pid = Process.pid
|
114
131
|
sf.control_url = @options[:control_url]
|
115
132
|
sf.control_auth_token = @options[:control_auth_token]
|
133
|
+
sf.running_from = File.expand_path('.')
|
116
134
|
|
117
|
-
sf.save path
|
135
|
+
sf.save path, permission
|
118
136
|
end
|
119
137
|
|
120
138
|
# Delete the configured pidfile
|
@@ -150,18 +168,20 @@ module Puma
|
|
150
168
|
true
|
151
169
|
end
|
152
170
|
|
171
|
+
# Begin a refork if supported
|
172
|
+
def refork
|
173
|
+
if clustered? && @runner.respond_to?(:fork_worker!) && @options[:fork_worker]
|
174
|
+
@runner.fork_worker!
|
175
|
+
true
|
176
|
+
else
|
177
|
+
log "* refork called but not available."
|
178
|
+
false
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
153
182
|
# Run the server. This blocks until the server is stopped
|
154
183
|
def run
|
155
|
-
previous_env =
|
156
|
-
if defined?(Bundler)
|
157
|
-
env = Bundler::ORIGINAL_ENV.dup
|
158
|
-
# add -rbundler/setup so we load from Gemfile when restarting
|
159
|
-
bundle = "-rbundler/setup"
|
160
|
-
env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
|
161
|
-
env
|
162
|
-
else
|
163
|
-
ENV.to_h
|
164
|
-
end
|
184
|
+
previous_env = get_env
|
165
185
|
|
166
186
|
@config.clamp
|
167
187
|
|
@@ -169,29 +189,21 @@ module Puma
|
|
169
189
|
|
170
190
|
setup_signals
|
171
191
|
set_process_title
|
192
|
+
|
193
|
+
# This blocks until the server is stopped
|
172
194
|
@runner.run
|
173
195
|
|
174
|
-
|
175
|
-
when :halt
|
176
|
-
log "* Stopping immediately!"
|
177
|
-
when :run, :stop
|
178
|
-
graceful_stop
|
179
|
-
when :restart
|
180
|
-
log "* Restarting..."
|
181
|
-
ENV.replace(previous_env)
|
182
|
-
@runner.before_restart
|
183
|
-
restart!
|
184
|
-
when :exit
|
185
|
-
# nothing
|
186
|
-
end
|
187
|
-
@binder.close_unix_paths
|
196
|
+
do_run_finished(previous_env)
|
188
197
|
end
|
189
198
|
|
190
|
-
# Return
|
191
|
-
|
192
|
-
|
199
|
+
# Return all tcp ports the launcher may be using, TCP or SSL
|
200
|
+
# @!attribute [r] connected_ports
|
201
|
+
# @version 5.0.0
|
202
|
+
def connected_ports
|
203
|
+
@binder.connected_ports
|
193
204
|
end
|
194
205
|
|
206
|
+
# @!attribute [r] restart_args
|
195
207
|
def restart_args
|
196
208
|
cmd = @options[:restart_cmd]
|
197
209
|
if cmd
|
@@ -202,35 +214,79 @@ module Puma
|
|
202
214
|
end
|
203
215
|
|
204
216
|
def close_binder_listeners
|
217
|
+
@runner.close_control_listeners
|
205
218
|
@binder.close_listeners
|
219
|
+
unless @status == :restart
|
220
|
+
log "=== puma shutdown: #{Time.now} ==="
|
221
|
+
log "- Goodbye!"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# @!attribute [r] thread_status
|
226
|
+
# @version 5.0.0
|
227
|
+
def thread_status
|
228
|
+
Thread.list.each do |thread|
|
229
|
+
name = "Thread: TID-#{thread.object_id.to_s(36)}"
|
230
|
+
name += " #{thread['label']}" if thread['label']
|
231
|
+
name += " #{thread.name}" if thread.respond_to?(:name) && thread.name
|
232
|
+
backtrace = thread.backtrace || ["<no backtrace available>"]
|
233
|
+
|
234
|
+
yield name, backtrace
|
235
|
+
end
|
206
236
|
end
|
207
237
|
|
208
238
|
private
|
209
239
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
240
|
+
def get_env
|
241
|
+
if defined?(Bundler)
|
242
|
+
env = Bundler::ORIGINAL_ENV.dup
|
243
|
+
# add -rbundler/setup so we load from Gemfile when restarting
|
244
|
+
bundle = "-rbundler/setup"
|
245
|
+
env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
|
246
|
+
env
|
247
|
+
else
|
248
|
+
ENV.to_h
|
249
|
+
end
|
250
|
+
end
|
215
251
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
252
|
+
def do_run_finished(previous_env)
|
253
|
+
case @status
|
254
|
+
when :halt
|
255
|
+
do_forceful_stop
|
256
|
+
when :run, :stop
|
257
|
+
do_graceful_stop
|
258
|
+
when :restart
|
259
|
+
do_restart(previous_env)
|
220
260
|
end
|
261
|
+
|
262
|
+
close_binder_listeners unless @status == :restart
|
221
263
|
end
|
222
264
|
|
223
|
-
def
|
224
|
-
|
265
|
+
def do_forceful_stop
|
266
|
+
log "* Stopping immediately!"
|
267
|
+
@runner.stop_control
|
268
|
+
end
|
269
|
+
|
270
|
+
def do_graceful_stop
|
271
|
+
@events.fire_on_stopped!
|
272
|
+
@runner.stop_blocked
|
273
|
+
end
|
274
|
+
|
275
|
+
def do_restart(previous_env)
|
276
|
+
log "* Restarting..."
|
277
|
+
ENV.replace(previous_env)
|
278
|
+
@runner.stop_control
|
279
|
+
restart!
|
225
280
|
end
|
226
281
|
|
227
282
|
def restart!
|
228
|
-
@
|
283
|
+
@events.fire_on_restart!
|
284
|
+
@config.run_hooks :on_restart, self, @log_writer
|
229
285
|
|
230
286
|
if Puma.jruby?
|
231
287
|
close_binder_listeners
|
232
288
|
|
233
|
-
|
289
|
+
require_relative 'jruby_restart'
|
234
290
|
JRubyRestart.chdir_exec(@restart_dir, restart_args)
|
235
291
|
elsif Puma.windows?
|
236
292
|
close_binder_listeners
|
@@ -241,71 +297,30 @@ module Puma
|
|
241
297
|
else
|
242
298
|
argv = restart_args
|
243
299
|
Dir.chdir(@restart_dir)
|
300
|
+
ENV.update(@binder.redirects_for_restart_env)
|
244
301
|
argv += [@binder.redirects_for_restart]
|
245
302
|
Kernel.exec(*argv)
|
246
303
|
end
|
247
304
|
end
|
248
305
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
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)
|
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
|
276
|
-
|
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
|
281
|
-
log "! Unable to prune Bundler environment, continuing"
|
282
|
-
return
|
283
|
-
end
|
284
|
-
|
285
|
-
deps, dirs = dependencies_and_files_to_require_after_prune
|
286
|
-
|
287
|
-
log '* Pruning Bundler environment'
|
288
|
-
home = ENV['GEM_HOME']
|
289
|
-
Bundler.with_clean_env do
|
290
|
-
ENV['GEM_HOME'] = home
|
291
|
-
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
292
|
-
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
293
|
-
# Ruby 2.0+ defaults to true which breaks socket activation
|
294
|
-
args += [{:close_others => false}]
|
295
|
-
Kernel.exec(*args)
|
306
|
+
# If configured, write the pid of the current process out
|
307
|
+
# to a file.
|
308
|
+
def write_pid
|
309
|
+
path = @options[:pidfile]
|
310
|
+
return unless path
|
311
|
+
cur_pid = Process.pid
|
312
|
+
File.write path, cur_pid, mode: 'wb:UTF-8'
|
313
|
+
at_exit do
|
314
|
+
delete_pidfile if cur_pid == Process.pid
|
296
315
|
end
|
297
316
|
end
|
298
317
|
|
299
|
-
def
|
300
|
-
|
301
|
-
end
|
302
|
-
|
303
|
-
def require_paths_for_gem(gem_spec)
|
304
|
-
gem_spec.full_require_paths
|
318
|
+
def reload_worker_directory
|
319
|
+
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
305
320
|
end
|
306
321
|
|
307
322
|
def log(str)
|
308
|
-
@
|
323
|
+
@log_writer.log(str)
|
309
324
|
end
|
310
325
|
|
311
326
|
def clustered?
|
@@ -313,35 +328,15 @@ module Puma
|
|
313
328
|
end
|
314
329
|
|
315
330
|
def unsupported(str)
|
316
|
-
@
|
331
|
+
@log_writer.error(str)
|
317
332
|
raise UnsupportedOption
|
318
333
|
end
|
319
334
|
|
320
|
-
def graceful_stop
|
321
|
-
@runner.stop_blocked
|
322
|
-
log "=== puma shutdown: #{Time.now} ==="
|
323
|
-
log "- Goodbye!"
|
324
|
-
end
|
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
|
-
|
341
335
|
def set_process_title
|
342
336
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
343
337
|
end
|
344
338
|
|
339
|
+
# @!attribute [r] title
|
345
340
|
def title
|
346
341
|
buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
|
347
342
|
buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
@@ -353,6 +348,7 @@ module Puma
|
|
353
348
|
ENV['RACK_ENV'] = environment
|
354
349
|
end
|
355
350
|
|
351
|
+
# @!attribute [r] environment
|
356
352
|
def environment
|
357
353
|
@environment
|
358
354
|
end
|
@@ -361,6 +357,11 @@ module Puma
|
|
361
357
|
@options[:prune_bundler] && clustered? && !@options[:preload_app]
|
362
358
|
end
|
363
359
|
|
360
|
+
def prune_bundler!
|
361
|
+
return unless prune_bundler?
|
362
|
+
BundlePruner.new(@original_argv, @options[:extra_runtime_dependencies], @log_writer).prune
|
363
|
+
end
|
364
|
+
|
364
365
|
def generate_restart_data
|
365
366
|
if dir = @options[:directory]
|
366
367
|
@restart_dir = dir
|
@@ -427,7 +428,8 @@ module Puma
|
|
427
428
|
|
428
429
|
begin
|
429
430
|
Signal.trap "SIGTERM" do
|
430
|
-
|
431
|
+
# Shortcut the control flow in case raise_exception_on_sigterm is true
|
432
|
+
do_graceful_stop
|
431
433
|
|
432
434
|
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
433
435
|
end
|
@@ -456,8 +458,13 @@ module Puma
|
|
456
458
|
end
|
457
459
|
|
458
460
|
begin
|
459
|
-
|
460
|
-
|
461
|
+
unless Puma.jruby? # INFO in use by JVM already
|
462
|
+
Signal.trap "SIGINFO" do
|
463
|
+
thread_status do |name, backtrace|
|
464
|
+
@log_writer.log(name)
|
465
|
+
@log_writer.log(backtrace.map { |bt| " #{bt}" })
|
466
|
+
end
|
467
|
+
end
|
461
468
|
end
|
462
469
|
rescue Exception
|
463
470
|
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
|
@@ -465,11 +472,13 @@ module Puma
|
|
465
472
|
end
|
466
473
|
end
|
467
474
|
|
468
|
-
def
|
469
|
-
|
475
|
+
def log_config
|
476
|
+
log "Configuration:"
|
477
|
+
|
478
|
+
@config.final_options
|
479
|
+
.each { |config_key, value| log "- #{config_key}: #{value}" }
|
470
480
|
|
471
|
-
|
472
|
-
"You must have RubyGems #{min_version}+ to use this feature."
|
481
|
+
log "\n"
|
473
482
|
end
|
474
483
|
end
|
475
484
|
end
|
@@ -0,0 +1,147 @@
|
|
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, :custom_logger
|
32
|
+
|
33
|
+
# Create a LogWriter that prints to +stdout+ and +stderr+.
|
34
|
+
def initialize(stdout, stderr)
|
35
|
+
@formatter = DefaultFormatter.new
|
36
|
+
@custom_logger = nil
|
37
|
+
@stdout = stdout
|
38
|
+
@stderr = stderr
|
39
|
+
|
40
|
+
@debug = ENV.key?('PUMA_DEBUG')
|
41
|
+
@error_logger = ErrorLogger.new(@stderr)
|
42
|
+
end
|
43
|
+
|
44
|
+
DEFAULT = new(STDOUT, STDERR)
|
45
|
+
|
46
|
+
# Returns an LogWriter object which writes its status to
|
47
|
+
# two StringIO objects.
|
48
|
+
def self.strings
|
49
|
+
LogWriter.new(StringIO.new, StringIO.new)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.stdio
|
53
|
+
LogWriter.new($stdout, $stderr)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.null
|
57
|
+
n = NullIO.new
|
58
|
+
LogWriter.new(n, n)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Write +str+ to +@stdout+
|
62
|
+
def log(str)
|
63
|
+
if @custom_logger&.respond_to?(:write)
|
64
|
+
@custom_logger.write(format(str))
|
65
|
+
else
|
66
|
+
internal_write "#{@formatter.call str}\n"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def write(str)
|
71
|
+
internal_write @formatter.call(str)
|
72
|
+
end
|
73
|
+
|
74
|
+
def internal_write(str)
|
75
|
+
LOG_QUEUE << str
|
76
|
+
while (w_str = LOG_QUEUE.pop(true)) do
|
77
|
+
begin
|
78
|
+
@stdout.is_a?(IO) and @stdout.wait_writable(1)
|
79
|
+
@stdout.write w_str
|
80
|
+
@stdout.flush unless @stdout.sync
|
81
|
+
rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
|
82
|
+
# 'Invalid argument' (Errno::EINVAL) may be raised by flush
|
83
|
+
end
|
84
|
+
end
|
85
|
+
rescue ThreadError
|
86
|
+
end
|
87
|
+
private :internal_write
|
88
|
+
|
89
|
+
def debug?
|
90
|
+
@debug
|
91
|
+
end
|
92
|
+
|
93
|
+
def debug(str)
|
94
|
+
log("% #{str}") if @debug
|
95
|
+
end
|
96
|
+
|
97
|
+
# Write +str+ to +@stderr+
|
98
|
+
def error(str)
|
99
|
+
@error_logger.info(text: @formatter.call("ERROR: #{str}"))
|
100
|
+
exit 1
|
101
|
+
end
|
102
|
+
|
103
|
+
def format(str)
|
104
|
+
formatter.call(str)
|
105
|
+
end
|
106
|
+
|
107
|
+
# An HTTP connection error has occurred.
|
108
|
+
# +error+ a connection exception, +req+ the request,
|
109
|
+
# and +text+ additional info
|
110
|
+
# @version 5.0.0
|
111
|
+
def connection_error(error, req, text="HTTP connection error")
|
112
|
+
@error_logger.info(error: error, req: req, text: text)
|
113
|
+
end
|
114
|
+
|
115
|
+
# An HTTP parse error has occurred.
|
116
|
+
# +error+ a parsing exception,
|
117
|
+
# and +req+ the request.
|
118
|
+
def parse_error(error, req)
|
119
|
+
@error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
|
120
|
+
end
|
121
|
+
|
122
|
+
# An SSL error has occurred.
|
123
|
+
# @param error <Puma::MiniSSL::SSLError>
|
124
|
+
# @param ssl_socket <Puma::MiniSSL::Socket>
|
125
|
+
def ssl_error(error, ssl_socket)
|
126
|
+
peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
|
127
|
+
peercert = ssl_socket.peercert
|
128
|
+
subject = peercert&.subject
|
129
|
+
@error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
|
130
|
+
end
|
131
|
+
|
132
|
+
# An unknown error has occurred.
|
133
|
+
# +error+ an exception object, +req+ the request,
|
134
|
+
# and +text+ additional info
|
135
|
+
def unknown_error(error, req=nil, text="Unknown error")
|
136
|
+
@error_logger.info(error: error, req: req, text: text)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Log occurred error debug dump.
|
140
|
+
# +error+ an exception object, +req+ the request,
|
141
|
+
# and +text+ additional info
|
142
|
+
# @version 5.0.0
|
143
|
+
def debug_error(error, req=nil, text="")
|
144
|
+
@error_logger.debug(error: error, req: req, text: text)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|