puma 3.11.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 +5 -5
- data/History.md +1708 -422
- data/LICENSE +23 -20
- data/README.md +190 -64
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +59 -21
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +69 -58
- 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/docs/jungle/rc.d/README.md +74 -0
- data/docs/jungle/rc.d/puma +61 -0
- data/docs/jungle/rc.d/puma.conf +10 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +22 -12
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +47 -22
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +100 -115
- 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 -2
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +61 -3
- data/ext/puma_http11/http11_parser.c +106 -118
- data/ext/puma_http11/http11_parser.h +2 -2
- data/ext/puma_http11/http11_parser.java.rl +22 -38
- data/ext/puma_http11/http11_parser.rl +6 -4
- data/ext/puma_http11/http11_parser_common.rl +6 -6
- data/ext/puma_http11/mini_ssl.c +376 -93
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +250 -88
- data/ext/puma_http11/puma_http11.c +49 -57
- data/lib/puma/app/status.rb +71 -49
- data/lib/puma/binder.rb +243 -148
- data/lib/puma/cli.rb +50 -35
- data/lib/puma/client.rb +369 -232
- data/lib/puma/cluster/worker.rb +175 -0
- data/lib/puma/cluster/worker_handle.rb +97 -0
- data/lib/puma/cluster.rb +268 -235
- data/lib/puma/commonlogger.rb +4 -2
- data/lib/puma/configuration.rb +116 -88
- data/lib/puma/const.rb +49 -30
- data/lib/puma/control_cli.rb +124 -77
- data/lib/puma/detect.rb +33 -2
- data/lib/puma/dsl.rb +685 -138
- data/lib/puma/error_logger.rb +112 -0
- data/lib/puma/events.rb +17 -111
- data/lib/puma/io_buffer.rb +34 -5
- data/lib/puma/jruby_restart.rb +4 -59
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +197 -130
- data/lib/puma/log_writer.rb +137 -0
- data/lib/puma/minissl/context_builder.rb +92 -0
- data/lib/puma/minissl.rb +256 -70
- data/lib/puma/null_io.rb +20 -1
- data/lib/puma/plugin/tmp_restart.rb +3 -1
- data/lib/puma/plugin.rb +9 -13
- data/lib/puma/rack/builder.rb +8 -9
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +3 -1
- data/lib/puma/reactor.rb +90 -187
- data/lib/puma/request.rb +607 -0
- data/lib/puma/runner.rb +94 -71
- data/lib/puma/server.rb +336 -703
- data/lib/puma/single.rb +27 -72
- data/lib/puma/state_file.rb +46 -7
- data/lib/puma/systemd.rb +47 -0
- data/lib/puma/thread_pool.rb +185 -91
- data/lib/puma/util.rb +23 -10
- data/lib/puma.rb +68 -3
- data/lib/rack/handler/puma.rb +17 -14
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +53 -30
- data/ext/puma_http11/io_buffer.c +0 -155
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -23
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/lib/puma/tcp_logger.rb +0 -39
- data/tools/jungle/README.md +0 -13
- data/tools/jungle/init.d/README.md +0 -59
- 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,12 +1,12 @@
|
|
1
|
-
|
2
|
-
require 'puma/detect'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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'
|
10
10
|
|
11
11
|
module Puma
|
12
12
|
# Puma::Launcher is the single entry point for starting a Puma server based on user
|
@@ -16,17 +16,14 @@ module Puma
|
|
16
16
|
# It is responsible for either launching a cluster of Puma workers or a single
|
17
17
|
# puma server.
|
18
18
|
class Launcher
|
19
|
-
|
20
|
-
|
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,57 +37,69 @@ 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.import_from_env
|
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
|
+
|
62
|
+
if @config.options[:bind_to_activated_sockets]
|
63
|
+
@config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
|
64
|
+
@config.options[:binds],
|
65
|
+
@config.options[:bind_to_activated_sockets] == 'only'
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
61
69
|
@options = @config.options
|
62
70
|
@config.clamp
|
63
71
|
|
64
|
-
|
72
|
+
@log_writer.formatter = LogWriter::PidFormatter.new if clustered?
|
73
|
+
@log_writer.formatter = options[:log_formatter] if @options[:log_formatter]
|
65
74
|
|
66
|
-
|
67
|
-
unsupported 'worker mode not supported on JRuby or Windows'
|
68
|
-
end
|
75
|
+
generate_restart_data
|
69
76
|
|
70
|
-
if
|
71
|
-
unsupported
|
77
|
+
if clustered? && !Puma.forkable?
|
78
|
+
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
|
72
79
|
end
|
73
80
|
|
74
81
|
Dir.chdir(@restart_dir)
|
75
82
|
|
76
|
-
prune_bundler
|
83
|
+
prune_bundler!
|
77
84
|
|
78
85
|
@environment = @options[:environment] if @options[:environment]
|
79
86
|
set_rack_environment
|
80
87
|
|
81
88
|
if clustered?
|
82
|
-
@
|
83
|
-
@options[:logger] = @events
|
89
|
+
@options[:logger] = @log_writer
|
84
90
|
|
85
|
-
@runner = Cluster.new(self
|
91
|
+
@runner = Cluster.new(self)
|
86
92
|
else
|
87
|
-
@runner = Single.new(self
|
93
|
+
@runner = Single.new(self)
|
88
94
|
end
|
95
|
+
Puma.stats_object = @runner
|
89
96
|
|
90
97
|
@status = :run
|
98
|
+
|
99
|
+
log_config if ENV['PUMA_LOG_CONFIG']
|
91
100
|
end
|
92
101
|
|
93
|
-
attr_reader :binder, :events, :config, :options, :restart_dir
|
102
|
+
attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir
|
94
103
|
|
95
104
|
# Return stats about the server
|
96
105
|
def stats
|
@@ -103,16 +112,18 @@ module Puma
|
|
103
112
|
write_pid
|
104
113
|
|
105
114
|
path = @options[:state]
|
115
|
+
permission = @options[:state_permission]
|
106
116
|
return unless path
|
107
117
|
|
108
|
-
|
118
|
+
require_relative 'state_file'
|
109
119
|
|
110
120
|
sf = StateFile.new
|
111
121
|
sf.pid = Process.pid
|
112
122
|
sf.control_url = @options[:control_url]
|
113
123
|
sf.control_auth_token = @options[:control_auth_token]
|
124
|
+
sf.running_from = File.expand_path('.')
|
114
125
|
|
115
|
-
sf.save path
|
126
|
+
sf.save path, permission
|
116
127
|
end
|
117
128
|
|
118
129
|
# Delete the configured pidfile
|
@@ -121,19 +132,6 @@ module Puma
|
|
121
132
|
File.unlink(path) if path && File.exist?(path)
|
122
133
|
end
|
123
134
|
|
124
|
-
# If configured, write the pid of the current process out
|
125
|
-
# to a file.
|
126
|
-
def write_pid
|
127
|
-
path = @options[:pidfile]
|
128
|
-
return unless path
|
129
|
-
|
130
|
-
File.open(path, 'w') { |f| f.puts Process.pid }
|
131
|
-
cur = Process.pid
|
132
|
-
at_exit do
|
133
|
-
delete_pidfile if cur == Process.pid
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
135
|
# Begin async shutdown of the server
|
138
136
|
def halt
|
139
137
|
@status = :halt
|
@@ -161,18 +159,20 @@ module Puma
|
|
161
159
|
true
|
162
160
|
end
|
163
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
|
+
|
164
173
|
# Run the server. This blocks until the server is stopped
|
165
174
|
def run
|
166
|
-
previous_env =
|
167
|
-
if defined?(Bundler)
|
168
|
-
env = Bundler::ORIGINAL_ENV.dup
|
169
|
-
# add -rbundler/setup so we load from Gemfile when restarting
|
170
|
-
bundle = "-rbundler/setup"
|
171
|
-
env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
|
172
|
-
env
|
173
|
-
else
|
174
|
-
ENV.to_h
|
175
|
-
end
|
175
|
+
previous_env = get_env
|
176
176
|
|
177
177
|
@config.clamp
|
178
178
|
|
@@ -180,28 +180,22 @@ module Puma
|
|
180
180
|
|
181
181
|
setup_signals
|
182
182
|
set_process_title
|
183
|
+
integrate_with_systemd
|
184
|
+
|
185
|
+
# This blocks until the server is stopped
|
183
186
|
@runner.run
|
184
187
|
|
185
|
-
|
186
|
-
when :halt
|
187
|
-
log "* Stopping immediately!"
|
188
|
-
when :run, :stop
|
189
|
-
graceful_stop
|
190
|
-
when :restart
|
191
|
-
log "* Restarting..."
|
192
|
-
ENV.replace(previous_env)
|
193
|
-
@runner.before_restart
|
194
|
-
restart!
|
195
|
-
when :exit
|
196
|
-
# nothing
|
197
|
-
end
|
188
|
+
do_run_finished(previous_env)
|
198
189
|
end
|
199
190
|
|
200
|
-
# Return
|
201
|
-
|
202
|
-
|
191
|
+
# Return all tcp ports the launcher may be using, TCP or SSL
|
192
|
+
# @!attribute [r] connected_ports
|
193
|
+
# @version 5.0.0
|
194
|
+
def connected_ports
|
195
|
+
@binder.connected_ports
|
203
196
|
end
|
204
197
|
|
198
|
+
# @!attribute [r] restart_args
|
205
199
|
def restart_args
|
206
200
|
cmd = @options[:restart_cmd]
|
207
201
|
if cmd
|
@@ -211,19 +205,80 @@ module Puma
|
|
211
205
|
end
|
212
206
|
end
|
213
207
|
|
208
|
+
def close_binder_listeners
|
209
|
+
@runner.close_control_listeners
|
210
|
+
@binder.close_listeners
|
211
|
+
unless @status == :restart
|
212
|
+
log "=== puma shutdown: #{Time.now} ==="
|
213
|
+
log "- Goodbye!"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# @!attribute [r] thread_status
|
218
|
+
# @version 5.0.0
|
219
|
+
def thread_status
|
220
|
+
Thread.list.each do |thread|
|
221
|
+
name = "Thread: TID-#{thread.object_id.to_s(36)}"
|
222
|
+
name += " #{thread['label']}" if thread['label']
|
223
|
+
name += " #{thread.name}" if thread.respond_to?(:name) && thread.name
|
224
|
+
backtrace = thread.backtrace || ["<no backtrace available>"]
|
225
|
+
|
226
|
+
yield name, backtrace
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
214
230
|
private
|
215
231
|
|
216
|
-
def
|
217
|
-
|
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
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
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!
|
218
272
|
end
|
219
273
|
|
220
274
|
def restart!
|
221
|
-
@
|
275
|
+
@events.fire_on_restart!
|
276
|
+
@config.run_hooks :on_restart, self, @log_writer
|
222
277
|
|
223
278
|
if Puma.jruby?
|
224
279
|
close_binder_listeners
|
225
280
|
|
226
|
-
|
281
|
+
require_relative 'jruby_restart'
|
227
282
|
JRubyRestart.chdir_exec(@restart_dir, restart_args)
|
228
283
|
elsif Puma.windows?
|
229
284
|
close_binder_listeners
|
@@ -232,50 +287,53 @@ module Puma
|
|
232
287
|
Dir.chdir(@restart_dir)
|
233
288
|
Kernel.exec(*argv)
|
234
289
|
else
|
235
|
-
redirects = {:close_others => true}
|
236
|
-
@binder.listeners.each_with_index do |(l, io), i|
|
237
|
-
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
238
|
-
redirects[io.to_i] = io.to_i
|
239
|
-
end
|
240
|
-
|
241
290
|
argv = restart_args
|
242
291
|
Dir.chdir(@restart_dir)
|
243
|
-
|
292
|
+
ENV.update(@binder.redirects_for_restart_env)
|
293
|
+
argv += [@binder.redirects_for_restart]
|
244
294
|
Kernel.exec(*argv)
|
245
295
|
end
|
246
296
|
end
|
247
297
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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
|
307
|
+
end
|
308
|
+
end
|
253
309
|
|
254
|
-
|
255
|
-
|
310
|
+
def reload_worker_directory
|
311
|
+
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
312
|
+
end
|
313
|
+
|
314
|
+
# Puma's systemd integration allows Puma to inform systemd:
|
315
|
+
# 1. when it has successfully started
|
316
|
+
# 2. when it is starting shutdown
|
317
|
+
# 3. periodically for a liveness check with a watchdog thread
|
318
|
+
def integrate_with_systemd
|
319
|
+
return unless ENV["NOTIFY_SOCKET"]
|
320
|
+
|
321
|
+
begin
|
322
|
+
require_relative 'systemd'
|
323
|
+
rescue LoadError
|
324
|
+
log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
|
256
325
|
return
|
257
326
|
end
|
258
327
|
|
259
|
-
|
260
|
-
spec = Bundler.rubygems.loaded_specs(d.name)
|
261
|
-
"#{d.name}:#{spec.version.to_s}"
|
262
|
-
end
|
328
|
+
log "* Enabling systemd notification integration"
|
263
329
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
ENV['GEM_HOME'] = home
|
268
|
-
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
269
|
-
wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
|
270
|
-
args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
271
|
-
# Ruby 2.0+ defaults to true which breaks socket activation
|
272
|
-
args += [{:close_others => false}] if RUBY_VERSION >= '2.0'
|
273
|
-
Kernel.exec(*args)
|
274
|
-
end
|
330
|
+
systemd = Systemd.new(@log_writer, @events)
|
331
|
+
systemd.hook_events
|
332
|
+
systemd.start_watchdog
|
275
333
|
end
|
276
334
|
|
277
335
|
def log(str)
|
278
|
-
@
|
336
|
+
@log_writer.log(str)
|
279
337
|
end
|
280
338
|
|
281
339
|
def clustered?
|
@@ -283,20 +341,15 @@ module Puma
|
|
283
341
|
end
|
284
342
|
|
285
343
|
def unsupported(str)
|
286
|
-
@
|
344
|
+
@log_writer.error(str)
|
287
345
|
raise UnsupportedOption
|
288
346
|
end
|
289
347
|
|
290
|
-
def graceful_stop
|
291
|
-
@runner.stop_blocked
|
292
|
-
log "=== puma shutdown: #{Time.now} ==="
|
293
|
-
log "- Goodbye!"
|
294
|
-
end
|
295
|
-
|
296
348
|
def set_process_title
|
297
349
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
298
350
|
end
|
299
351
|
|
352
|
+
# @!attribute [r] title
|
300
353
|
def title
|
301
354
|
buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
|
302
355
|
buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
@@ -308,6 +361,7 @@ module Puma
|
|
308
361
|
ENV['RACK_ENV'] = environment
|
309
362
|
end
|
310
363
|
|
364
|
+
# @!attribute [r] environment
|
311
365
|
def environment
|
312
366
|
@environment
|
313
367
|
end
|
@@ -316,16 +370,11 @@ module Puma
|
|
316
370
|
@options[:prune_bundler] && clustered? && !@options[:preload_app]
|
317
371
|
end
|
318
372
|
|
319
|
-
def
|
320
|
-
|
321
|
-
|
322
|
-
uri = URI.parse(l)
|
323
|
-
next unless uri.scheme == 'unix'
|
324
|
-
File.unlink("#{uri.host}#{uri.path}")
|
325
|
-
end
|
373
|
+
def prune_bundler!
|
374
|
+
return unless prune_bundler?
|
375
|
+
BundlePruner.new(@original_argv, @options[:extra_runtime_dependencies], @log_writer).prune
|
326
376
|
end
|
327
377
|
|
328
|
-
|
329
378
|
def generate_restart_data
|
330
379
|
if dir = @options[:directory]
|
331
380
|
@restart_dir = dir
|
@@ -392,9 +441,10 @@ module Puma
|
|
392
441
|
|
393
442
|
begin
|
394
443
|
Signal.trap "SIGTERM" do
|
395
|
-
|
444
|
+
# Shortcut the control flow in case raise_exception_on_sigterm is true
|
445
|
+
do_graceful_stop
|
396
446
|
|
397
|
-
raise
|
447
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
398
448
|
end
|
399
449
|
rescue Exception
|
400
450
|
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
|
@@ -402,12 +452,6 @@ module Puma
|
|
402
452
|
|
403
453
|
begin
|
404
454
|
Signal.trap "SIGINT" do
|
405
|
-
if Puma.jruby?
|
406
|
-
@status = :exit
|
407
|
-
graceful_stop
|
408
|
-
exit
|
409
|
-
end
|
410
|
-
|
411
455
|
stop
|
412
456
|
end
|
413
457
|
rescue Exception
|
@@ -425,6 +469,29 @@ module Puma
|
|
425
469
|
rescue Exception
|
426
470
|
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
|
427
471
|
end
|
472
|
+
|
473
|
+
begin
|
474
|
+
unless Puma.jruby? # INFO in use by JVM already
|
475
|
+
Signal.trap "SIGINFO" do
|
476
|
+
thread_status do |name, backtrace|
|
477
|
+
@log_writer.log(name)
|
478
|
+
@log_writer.log(backtrace.map { |bt| " #{bt}" })
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
rescue Exception
|
483
|
+
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
|
484
|
+
# to see this constantly on Linux.
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
def log_config
|
489
|
+
log "Configuration:"
|
490
|
+
|
491
|
+
@config.final_options
|
492
|
+
.each { |config_key, value| log "- #{config_key}: #{value}" }
|
493
|
+
|
494
|
+
log "\n"
|
428
495
|
end
|
429
496
|
end
|
430
497
|
end
|
@@ -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
|