puma 3.12.6 → 6.3.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 +1806 -451
- data/LICENSE +23 -20
- data/README.md +217 -65
- 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/{tools → docs}/jungle/rc.d/README.md +1 -1
- data/{tools → docs}/jungle/rc.d/puma +2 -2
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +2 -2
- 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 +94 -120
- 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 +103 -117
- 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 +3 -3
- data/ext/puma_http11/http11_parser_common.rl +6 -6
- data/ext/puma_http11/mini_ssl.c +389 -99
- 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 +248 -92
- data/ext/puma_http11/puma_http11.c +49 -57
- data/lib/puma/app/status.rb +71 -49
- data/lib/puma/binder.rb +244 -150
- data/lib/puma/cli.rb +38 -34
- data/lib/puma/client.rb +388 -244
- data/lib/puma/cluster/worker.rb +180 -0
- data/lib/puma/cluster/worker_handle.rb +97 -0
- data/lib/puma/cluster.rb +261 -243
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +116 -88
- data/lib/puma/const.rb +154 -104
- data/lib/puma/control_cli.rb +115 -70
- data/lib/puma/detect.rb +33 -2
- data/lib/puma/dsl.rb +764 -134
- data/lib/puma/error_logger.rb +113 -0
- data/lib/puma/events.rb +16 -112
- data/lib/puma/io_buffer.rb +42 -5
- 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 +184 -133
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +93 -0
- data/lib/puma/minissl.rb +263 -70
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +3 -1
- data/lib/puma/plugin.rb +7 -13
- data/lib/puma/rack/builder.rb +9 -11
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +21 -4
- data/lib/puma/reactor.rb +93 -315
- data/lib/puma/request.rb +671 -0
- data/lib/puma/runner.rb +94 -69
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +327 -772
- data/lib/puma/single.rb +20 -74
- data/lib/puma/state_file.rb +45 -8
- data/lib/puma/thread_pool.rb +146 -92
- data/lib/puma/util.rb +22 -10
- data/lib/puma.rb +60 -5
- data/lib/rack/handler/puma.rb +116 -90
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +54 -32
- 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 -25
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
- data/lib/puma/java_io_buffer.rb +0 -47
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- 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/{tools → docs}/jungle/rc.d/puma.conf +0 -0
data/lib/puma/launcher.rb
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
require 'puma/binder'
|
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'
|
12
10
|
|
13
11
|
module Puma
|
14
12
|
# Puma::Launcher is the single entry point for starting a Puma server based on user
|
@@ -18,17 +16,14 @@ module Puma
|
|
18
16
|
# It is responsible for either launching a cluster of Puma workers or a single
|
19
17
|
# puma server.
|
20
18
|
class Launcher
|
21
|
-
|
22
|
-
|
23
|
-
:before_worker_shutdown, :before_worker_boot, :before_worker_fork,
|
24
|
-
:after_worker_boot, :before_fork, :on_restart
|
25
|
-
]
|
19
|
+
autoload :BundlePruner, 'puma/launcher/bundle_pruner'
|
20
|
+
|
26
21
|
# Returns an instance of Launcher
|
27
22
|
#
|
28
23
|
# +conf+ A Puma::Configuration object indicating how to run the server.
|
29
24
|
#
|
30
25
|
# +launcher_args+ A Hash that currently has one required key `:events`,
|
31
|
-
# 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`,
|
32
27
|
# this object will be responsible for broadcasting Puma's internal state
|
33
28
|
# to a logging destination. An optional key `:argv` can be supplied,
|
34
29
|
# this should be an array of strings, these arguments are re-used when
|
@@ -42,58 +37,78 @@ module Puma
|
|
42
37
|
# [200, {}, ["hello world"]]
|
43
38
|
# end
|
44
39
|
# end
|
45
|
-
# Puma::Launcher.new(conf,
|
40
|
+
# Puma::Launcher.new(conf, log_writer: Puma::LogWriter.stdio).run
|
46
41
|
def initialize(conf, launcher_args={})
|
47
42
|
@runner = nil
|
48
|
-
@
|
43
|
+
@log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
|
44
|
+
@events = launcher_args[:events] || Events.new
|
49
45
|
@argv = launcher_args[:argv] || []
|
50
46
|
@original_argv = @argv.dup
|
51
47
|
@config = conf
|
52
48
|
|
53
|
-
@
|
54
|
-
@binder.import_from_env
|
55
|
-
|
56
|
-
@environment = conf.environment
|
49
|
+
@config.options[:log_writer] = @log_writer
|
57
50
|
|
58
51
|
# Advertise the Configuration
|
59
52
|
Puma.cli_config = @config if defined?(Puma.cli_config)
|
60
53
|
|
61
54
|
@config.load
|
62
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
|
+
|
63
76
|
@options = @config.options
|
64
77
|
@config.clamp
|
65
78
|
|
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]
|
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
|
-
@
|
85
|
-
@options[:logger] = @events
|
98
|
+
@options[:logger] = @log_writer
|
86
99
|
|
87
|
-
@runner = Cluster.new(self
|
100
|
+
@runner = Cluster.new(self)
|
88
101
|
else
|
89
|
-
@runner = Single.new(self
|
102
|
+
@runner = Single.new(self)
|
90
103
|
end
|
91
104
|
Puma.stats_object = @runner
|
92
105
|
|
93
106
|
@status = :run
|
107
|
+
|
108
|
+
log_config if ENV['PUMA_LOG_CONFIG']
|
94
109
|
end
|
95
110
|
|
96
|
-
attr_reader :binder, :events, :config, :options, :restart_dir
|
111
|
+
attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir
|
97
112
|
|
98
113
|
# Return stats about the server
|
99
114
|
def stats
|
@@ -106,16 +121,18 @@ module Puma
|
|
106
121
|
write_pid
|
107
122
|
|
108
123
|
path = @options[:state]
|
124
|
+
permission = @options[:state_permission]
|
109
125
|
return unless path
|
110
126
|
|
111
|
-
|
127
|
+
require_relative 'state_file'
|
112
128
|
|
113
129
|
sf = StateFile.new
|
114
130
|
sf.pid = Process.pid
|
115
131
|
sf.control_url = @options[:control_url]
|
116
132
|
sf.control_auth_token = @options[:control_auth_token]
|
133
|
+
sf.running_from = File.expand_path('.')
|
117
134
|
|
118
|
-
sf.save path
|
135
|
+
sf.save path, permission
|
119
136
|
end
|
120
137
|
|
121
138
|
# Delete the configured pidfile
|
@@ -124,19 +141,6 @@ module Puma
|
|
124
141
|
File.unlink(path) if path && File.exist?(path)
|
125
142
|
end
|
126
143
|
|
127
|
-
# If configured, write the pid of the current process out
|
128
|
-
# to a file.
|
129
|
-
def write_pid
|
130
|
-
path = @options[:pidfile]
|
131
|
-
return unless path
|
132
|
-
|
133
|
-
File.open(path, 'w') { |f| f.puts Process.pid }
|
134
|
-
cur = Process.pid
|
135
|
-
at_exit do
|
136
|
-
delete_pidfile if cur == Process.pid
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
144
|
# Begin async shutdown of the server
|
141
145
|
def halt
|
142
146
|
@status = :halt
|
@@ -164,18 +168,20 @@ module Puma
|
|
164
168
|
true
|
165
169
|
end
|
166
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
|
+
|
167
182
|
# Run the server. This blocks until the server is stopped
|
168
183
|
def run
|
169
|
-
previous_env =
|
170
|
-
if defined?(Bundler)
|
171
|
-
env = Bundler::ORIGINAL_ENV.dup
|
172
|
-
# add -rbundler/setup so we load from Gemfile when restarting
|
173
|
-
bundle = "-rbundler/setup"
|
174
|
-
env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
|
175
|
-
env
|
176
|
-
else
|
177
|
-
ENV.to_h
|
178
|
-
end
|
184
|
+
previous_env = get_env
|
179
185
|
|
180
186
|
@config.clamp
|
181
187
|
|
@@ -183,28 +189,21 @@ module Puma
|
|
183
189
|
|
184
190
|
setup_signals
|
185
191
|
set_process_title
|
192
|
+
|
193
|
+
# This blocks until the server is stopped
|
186
194
|
@runner.run
|
187
195
|
|
188
|
-
|
189
|
-
when :halt
|
190
|
-
log "* Stopping immediately!"
|
191
|
-
when :run, :stop
|
192
|
-
graceful_stop
|
193
|
-
when :restart
|
194
|
-
log "* Restarting..."
|
195
|
-
ENV.replace(previous_env)
|
196
|
-
@runner.before_restart
|
197
|
-
restart!
|
198
|
-
when :exit
|
199
|
-
# nothing
|
200
|
-
end
|
196
|
+
do_run_finished(previous_env)
|
201
197
|
end
|
202
198
|
|
203
|
-
# Return
|
204
|
-
|
205
|
-
|
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
|
206
204
|
end
|
207
205
|
|
206
|
+
# @!attribute [r] restart_args
|
208
207
|
def restart_args
|
209
208
|
cmd = @options[:restart_cmd]
|
210
209
|
if cmd
|
@@ -214,19 +213,80 @@ module Puma
|
|
214
213
|
end
|
215
214
|
end
|
216
215
|
|
216
|
+
def close_binder_listeners
|
217
|
+
@runner.close_control_listeners
|
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
|
236
|
+
end
|
237
|
+
|
217
238
|
private
|
218
239
|
|
219
|
-
def
|
220
|
-
|
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
|
251
|
+
|
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)
|
260
|
+
end
|
261
|
+
|
262
|
+
close_binder_listeners unless @status == :restart
|
263
|
+
end
|
264
|
+
|
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!
|
221
280
|
end
|
222
281
|
|
223
282
|
def restart!
|
224
|
-
@
|
283
|
+
@events.fire_on_restart!
|
284
|
+
@config.run_hooks :on_restart, self, @log_writer
|
225
285
|
|
226
286
|
if Puma.jruby?
|
227
287
|
close_binder_listeners
|
228
288
|
|
229
|
-
|
289
|
+
require_relative 'jruby_restart'
|
230
290
|
JRubyRestart.chdir_exec(@restart_dir, restart_args)
|
231
291
|
elsif Puma.windows?
|
232
292
|
close_binder_listeners
|
@@ -235,50 +295,32 @@ module Puma
|
|
235
295
|
Dir.chdir(@restart_dir)
|
236
296
|
Kernel.exec(*argv)
|
237
297
|
else
|
238
|
-
redirects = {:close_others => true}
|
239
|
-
@binder.listeners.each_with_index do |(l, io), i|
|
240
|
-
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
241
|
-
redirects[io.to_i] = io.to_i
|
242
|
-
end
|
243
|
-
|
244
298
|
argv = restart_args
|
245
299
|
Dir.chdir(@restart_dir)
|
246
|
-
|
300
|
+
ENV.update(@binder.redirects_for_restart_env)
|
301
|
+
argv += [@binder.redirects_for_restart]
|
247
302
|
Kernel.exec(*argv)
|
248
303
|
end
|
249
304
|
end
|
250
305
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
end
|
261
|
-
|
262
|
-
deps = puma.runtime_dependencies.map do |d|
|
263
|
-
spec = Bundler.rubygems.loaded_specs(d.name)
|
264
|
-
"#{d.name}:#{spec.version.to_s}"
|
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
|
265
315
|
end
|
316
|
+
end
|
266
317
|
|
267
|
-
|
268
|
-
|
269
|
-
Bundler.with_clean_env do
|
270
|
-
ENV['GEM_HOME'] = home
|
271
|
-
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
272
|
-
wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
|
273
|
-
args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
274
|
-
# Ruby 2.0+ defaults to true which breaks socket activation
|
275
|
-
args += [{:close_others => false}] if RUBY_VERSION >= '2.0'
|
276
|
-
Kernel.exec(*args)
|
277
|
-
end
|
318
|
+
def reload_worker_directory
|
319
|
+
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
278
320
|
end
|
279
321
|
|
280
322
|
def log(str)
|
281
|
-
@
|
323
|
+
@log_writer.log(str)
|
282
324
|
end
|
283
325
|
|
284
326
|
def clustered?
|
@@ -286,20 +328,15 @@ module Puma
|
|
286
328
|
end
|
287
329
|
|
288
330
|
def unsupported(str)
|
289
|
-
@
|
331
|
+
@log_writer.error(str)
|
290
332
|
raise UnsupportedOption
|
291
333
|
end
|
292
334
|
|
293
|
-
def graceful_stop
|
294
|
-
@runner.stop_blocked
|
295
|
-
log "=== puma shutdown: #{Time.now} ==="
|
296
|
-
log "- Goodbye!"
|
297
|
-
end
|
298
|
-
|
299
335
|
def set_process_title
|
300
336
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
301
337
|
end
|
302
338
|
|
339
|
+
# @!attribute [r] title
|
303
340
|
def title
|
304
341
|
buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
|
305
342
|
buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
@@ -311,6 +348,7 @@ module Puma
|
|
311
348
|
ENV['RACK_ENV'] = environment
|
312
349
|
end
|
313
350
|
|
351
|
+
# @!attribute [r] environment
|
314
352
|
def environment
|
315
353
|
@environment
|
316
354
|
end
|
@@ -319,16 +357,11 @@ module Puma
|
|
319
357
|
@options[:prune_bundler] && clustered? && !@options[:preload_app]
|
320
358
|
end
|
321
359
|
|
322
|
-
def
|
323
|
-
|
324
|
-
|
325
|
-
uri = URI.parse(l)
|
326
|
-
next unless uri.scheme == 'unix'
|
327
|
-
File.unlink("#{uri.host}#{uri.path}")
|
328
|
-
end
|
360
|
+
def prune_bundler!
|
361
|
+
return unless prune_bundler?
|
362
|
+
BundlePruner.new(@original_argv, @options[:extra_runtime_dependencies], @log_writer).prune
|
329
363
|
end
|
330
364
|
|
331
|
-
|
332
365
|
def generate_restart_data
|
333
366
|
if dir = @options[:directory]
|
334
367
|
@restart_dir = dir
|
@@ -395,9 +428,10 @@ module Puma
|
|
395
428
|
|
396
429
|
begin
|
397
430
|
Signal.trap "SIGTERM" do
|
398
|
-
|
431
|
+
# Shortcut the control flow in case raise_exception_on_sigterm is true
|
432
|
+
do_graceful_stop
|
399
433
|
|
400
|
-
raise
|
434
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
401
435
|
end
|
402
436
|
rescue Exception
|
403
437
|
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
|
@@ -405,12 +439,6 @@ module Puma
|
|
405
439
|
|
406
440
|
begin
|
407
441
|
Signal.trap "SIGINT" do
|
408
|
-
if Puma.jruby?
|
409
|
-
@status = :exit
|
410
|
-
graceful_stop
|
411
|
-
exit
|
412
|
-
end
|
413
|
-
|
414
442
|
stop
|
415
443
|
end
|
416
444
|
rescue Exception
|
@@ -428,6 +456,29 @@ module Puma
|
|
428
456
|
rescue Exception
|
429
457
|
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
|
430
458
|
end
|
459
|
+
|
460
|
+
begin
|
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
|
468
|
+
end
|
469
|
+
rescue Exception
|
470
|
+
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
|
471
|
+
# to see this constantly on Linux.
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
def log_config
|
476
|
+
log "Configuration:"
|
477
|
+
|
478
|
+
@config.final_options
|
479
|
+
.each { |config_key, value| log "- #{config_key}: #{value}" }
|
480
|
+
|
481
|
+
log "\n"
|
431
482
|
end
|
432
483
|
end
|
433
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
|