puma 4.3.1 → 5.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 +94 -3
- data/LICENSE +23 -20
- data/README.md +26 -13
- data/docs/architecture.md +3 -3
- data/docs/deployment.md +9 -3
- data/docs/fork_worker.md +31 -0
- data/docs/jungle/README.md +13 -0
- data/{tools → docs}/jungle/rc.d/README.md +0 -0
- data/{tools → docs}/jungle/rc.d/puma +0 -0
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/{tools → docs}/jungle/upstart/README.md +0 -0
- data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
- data/{tools → docs}/jungle/upstart/puma.conf +0 -0
- data/docs/signals.md +7 -6
- data/docs/systemd.md +1 -63
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/extconf.rb +4 -3
- data/ext/puma_http11/http11_parser.c +3 -1
- data/ext/puma_http11/http11_parser.rl +3 -1
- data/ext/puma_http11/mini_ssl.c +15 -2
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
- data/ext/puma_http11/puma_http11.c +7 -38
- data/lib/puma.rb +17 -0
- data/lib/puma/app/status.rb +18 -3
- data/lib/puma/binder.rb +88 -68
- data/lib/puma/cli.rb +7 -15
- data/lib/puma/client.rb +67 -14
- data/lib/puma/cluster.rb +191 -74
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +31 -42
- data/lib/puma/const.rb +4 -3
- data/lib/puma/control_cli.rb +29 -17
- data/lib/puma/detect.rb +17 -0
- data/lib/puma/dsl.rb +144 -70
- data/lib/puma/error_logger.rb +97 -0
- data/lib/puma/events.rb +35 -31
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/launcher.rb +49 -31
- data/lib/puma/minissl.rb +60 -18
- data/lib/puma/minissl/context_builder.rb +0 -3
- data/lib/puma/null_io.rb +1 -1
- data/lib/puma/plugin.rb +1 -10
- data/lib/puma/rack/builder.rb +0 -4
- data/lib/puma/reactor.rb +9 -4
- data/lib/puma/runner.rb +8 -36
- data/lib/puma/server.rb +149 -186
- data/lib/puma/single.rb +7 -64
- data/lib/puma/state_file.rb +6 -3
- data/lib/puma/thread_pool.rb +94 -49
- data/lib/rack/handler/puma.rb +1 -3
- data/tools/{docker/Dockerfile → Dockerfile} +0 -0
- metadata +21 -23
- 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/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
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'puma/const'
|
4
|
+
|
5
|
+
module Puma
|
6
|
+
# The implementation of a detailed error logging.
|
7
|
+
# @version 5.0.0
|
8
|
+
#
|
9
|
+
class ErrorLogger
|
10
|
+
include Const
|
11
|
+
|
12
|
+
attr_reader :ioerr
|
13
|
+
|
14
|
+
REQUEST_FORMAT = %{"%s %s%s" - (%s)}
|
15
|
+
|
16
|
+
def initialize(ioerr)
|
17
|
+
@ioerr = ioerr
|
18
|
+
@ioerr.sync = true
|
19
|
+
|
20
|
+
@debug = ENV.key? 'PUMA_DEBUG'
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.stdio
|
24
|
+
new $stderr
|
25
|
+
end
|
26
|
+
|
27
|
+
# Print occured error details.
|
28
|
+
# +options+ hash with additional options:
|
29
|
+
# - +error+ is an exception object
|
30
|
+
# - +req+ the http request
|
31
|
+
# - +text+ (default nil) custom string to print in title
|
32
|
+
# and before all remaining info.
|
33
|
+
#
|
34
|
+
def info(options={})
|
35
|
+
ioerr.puts title(options)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Print occured error details only if
|
39
|
+
# environment variable PUMA_DEBUG is defined.
|
40
|
+
# +options+ hash with additional options:
|
41
|
+
# - +error+ is an exception object
|
42
|
+
# - +req+ the http request
|
43
|
+
# - +text+ (default nil) custom string to print in title
|
44
|
+
# and before all remaining info.
|
45
|
+
#
|
46
|
+
def debug(options={})
|
47
|
+
return unless @debug
|
48
|
+
|
49
|
+
error = options[:error]
|
50
|
+
req = options[:req]
|
51
|
+
|
52
|
+
string_block = []
|
53
|
+
string_block << title(options)
|
54
|
+
string_block << request_dump(req) if req
|
55
|
+
string_block << error_backtrace(options) if error
|
56
|
+
|
57
|
+
ioerr.puts string_block.join("\n")
|
58
|
+
end
|
59
|
+
|
60
|
+
def title(options={})
|
61
|
+
text = options[:text]
|
62
|
+
req = options[:req]
|
63
|
+
error = options[:error]
|
64
|
+
|
65
|
+
string_block = ["#{Time.now}"]
|
66
|
+
string_block << " #{text}" if text
|
67
|
+
string_block << " (#{request_title(req)})" if request_parsed?(req)
|
68
|
+
string_block << ": #{error.inspect}" if error
|
69
|
+
string_block.join('')
|
70
|
+
end
|
71
|
+
|
72
|
+
def request_dump(req)
|
73
|
+
"Headers: #{request_headers(req)}\n" \
|
74
|
+
"Body: #{req.body}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def request_title(req)
|
78
|
+
env = req.env
|
79
|
+
|
80
|
+
REQUEST_FORMAT % [
|
81
|
+
env[REQUEST_METHOD],
|
82
|
+
env[REQUEST_PATH] || env[PATH_INFO],
|
83
|
+
env[QUERY_STRING] || "",
|
84
|
+
env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
|
85
|
+
]
|
86
|
+
end
|
87
|
+
|
88
|
+
def request_headers(req)
|
89
|
+
headers = req.env.select { |key, _| key.start_with?('HTTP_') }
|
90
|
+
headers.map { |key, value| [key[5..-1], value] }.to_h.inspect
|
91
|
+
end
|
92
|
+
|
93
|
+
def request_parsed?(req)
|
94
|
+
req && req.env[REQUEST_METHOD]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/puma/events.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'puma/const'
|
4
3
|
require "puma/null_io"
|
4
|
+
require 'puma/error_logger'
|
5
5
|
require 'stringio'
|
6
6
|
|
7
7
|
module Puma
|
@@ -23,8 +23,6 @@ module Puma
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
include Const
|
27
|
-
|
28
26
|
# Create an Events object that prints to +stdout+ and +stderr+.
|
29
27
|
#
|
30
28
|
def initialize(stdout, stderr)
|
@@ -36,6 +34,7 @@ module Puma
|
|
36
34
|
@stderr.sync = true
|
37
35
|
|
38
36
|
@debug = ENV.key? 'PUMA_DEBUG'
|
37
|
+
@error_logger = ErrorLogger.new(@stderr)
|
39
38
|
|
40
39
|
@hooks = Hash.new { |h,k| h[k] = [] }
|
41
40
|
end
|
@@ -66,7 +65,8 @@ module Puma
|
|
66
65
|
# Write +str+ to +@stdout+
|
67
66
|
#
|
68
67
|
def log(str)
|
69
|
-
@stdout.puts format(str)
|
68
|
+
@stdout.puts format(str) if @stdout.respond_to? :puts
|
69
|
+
rescue Errno::EPIPE
|
70
70
|
end
|
71
71
|
|
72
72
|
def write(str)
|
@@ -80,7 +80,7 @@ module Puma
|
|
80
80
|
# Write +str+ to +@stderr+
|
81
81
|
#
|
82
82
|
def error(str)
|
83
|
-
@
|
83
|
+
@error_logger.info(text: format("ERROR: #{str}"))
|
84
84
|
exit 1
|
85
85
|
end
|
86
86
|
|
@@ -88,43 +88,47 @@ module Puma
|
|
88
88
|
formatter.call(str)
|
89
89
|
end
|
90
90
|
|
91
|
+
# An HTTP connection error has occurred.
|
92
|
+
# +error+ a connection exception, +req+ the request,
|
93
|
+
# and +text+ additional info
|
94
|
+
# @version 5.0.0
|
95
|
+
#
|
96
|
+
def connection_error(error, req, text="HTTP connection error")
|
97
|
+
@error_logger.info(error: error, req: req, text: text)
|
98
|
+
end
|
99
|
+
|
91
100
|
# An HTTP parse error has occurred.
|
92
|
-
# +
|
93
|
-
#
|
101
|
+
# +error+ a parsing exception,
|
102
|
+
# and +req+ the request.
|
94
103
|
#
|
95
|
-
def parse_error(
|
96
|
-
@
|
97
|
-
"(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \
|
98
|
-
"#{error.inspect}" \
|
99
|
-
"\n---\n"
|
104
|
+
def parse_error(error, req)
|
105
|
+
@error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
|
100
106
|
end
|
101
107
|
|
102
108
|
# An SSL error has occurred.
|
103
|
-
# +
|
104
|
-
# any peer certificate (if present)
|
109
|
+
# +error+ an exception object, +peeraddr+ peer address,
|
110
|
+
# and +peercert+ any peer certificate (if present).
|
105
111
|
#
|
106
|
-
def ssl_error(
|
112
|
+
def ssl_error(error, peeraddr, peercert)
|
107
113
|
subject = peercert ? peercert.subject : nil
|
108
|
-
@
|
114
|
+
@error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
|
109
115
|
end
|
110
116
|
|
111
117
|
# An unknown error has occurred.
|
112
|
-
# +
|
113
|
-
# +
|
118
|
+
# +error+ an exception object, +req+ the request,
|
119
|
+
# and +text+ additional info
|
114
120
|
#
|
115
|
-
def unknown_error(
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
@stderr.puts string_block.join("\n")
|
127
|
-
end
|
121
|
+
def unknown_error(error, req=nil, text="Unknown error")
|
122
|
+
@error_logger.info(error: error, req: req, text: text)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Log occurred error debug dump.
|
126
|
+
# +error+ an exception object, +req+ the request,
|
127
|
+
# and +text+ additional info
|
128
|
+
# @version 5.0.0
|
129
|
+
#
|
130
|
+
def debug_error(error, req=nil, text="")
|
131
|
+
@error_logger.debug(error: error, req: req, text: text)
|
128
132
|
end
|
129
133
|
|
130
134
|
def on_booted(&block)
|
data/lib/puma/io_buffer.rb
CHANGED
data/lib/puma/jruby_restart.rb
CHANGED
@@ -22,63 +22,5 @@ module Puma
|
|
22
22
|
execlp(cmd, *argv)
|
23
23
|
raise SystemCallError.new(FFI.errno)
|
24
24
|
end
|
25
|
-
|
26
|
-
PermKey = 'PUMA_DAEMON_PERM'
|
27
|
-
RestartKey = 'PUMA_DAEMON_RESTART'
|
28
|
-
|
29
|
-
# Called to tell things "Your now always in daemon mode,
|
30
|
-
# don't try to reenter it."
|
31
|
-
#
|
32
|
-
def self.perm_daemonize
|
33
|
-
ENV[PermKey] = "1"
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.daemon?
|
37
|
-
ENV.key?(PermKey) || ENV.key?(RestartKey)
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.daemon_init
|
41
|
-
return true if ENV.key?(PermKey)
|
42
|
-
|
43
|
-
return false unless ENV.key? RestartKey
|
44
|
-
|
45
|
-
master = ENV[RestartKey]
|
46
|
-
|
47
|
-
# In case the master disappears early
|
48
|
-
begin
|
49
|
-
Process.kill "SIGUSR2", master.to_i
|
50
|
-
rescue SystemCallError => e
|
51
|
-
end
|
52
|
-
|
53
|
-
ENV[RestartKey] = ""
|
54
|
-
|
55
|
-
setsid
|
56
|
-
|
57
|
-
null = File.open "/dev/null", "w+"
|
58
|
-
STDIN.reopen null
|
59
|
-
STDOUT.reopen null
|
60
|
-
STDERR.reopen null
|
61
|
-
|
62
|
-
true
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.daemon_start(dir, argv)
|
66
|
-
ENV[RestartKey] = Process.pid.to_s
|
67
|
-
|
68
|
-
if k = ENV['PUMA_JRUBY_DAEMON_OPTS']
|
69
|
-
ENV['JRUBY_OPTS'] = k
|
70
|
-
end
|
71
|
-
|
72
|
-
cmd = argv.first
|
73
|
-
argv = ([:string] * argv.size).zip(argv).flatten
|
74
|
-
argv << :string
|
75
|
-
argv << nil
|
76
|
-
|
77
|
-
chdir(dir)
|
78
|
-
ret = fork
|
79
|
-
return ret if ret != 0
|
80
|
-
execlp(cmd, *argv)
|
81
|
-
raise SystemCallError.new(FFI.errno)
|
82
|
-
end
|
83
25
|
end
|
84
26
|
end
|
data/lib/puma/launcher.rb
CHANGED
@@ -47,8 +47,9 @@ module Puma
|
|
47
47
|
@original_argv = @argv.dup
|
48
48
|
@config = conf
|
49
49
|
|
50
|
-
@binder = Binder.new(@events)
|
51
|
-
@binder.
|
50
|
+
@binder = Binder.new(@events, conf)
|
51
|
+
@binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
|
52
|
+
@binder.create_activated_fds(ENV).each { |k| ENV.delete k }
|
52
53
|
|
53
54
|
@environment = conf.environment
|
54
55
|
|
@@ -69,10 +70,6 @@ module Puma
|
|
69
70
|
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
|
70
71
|
end
|
71
72
|
|
72
|
-
if @options[:daemon] && Puma.windows?
|
73
|
-
unsupported 'daemon mode not supported on Windows'
|
74
|
-
end
|
75
|
-
|
76
73
|
Dir.chdir(@restart_dir)
|
77
74
|
|
78
75
|
prune_bundler if prune_bundler?
|
@@ -105,6 +102,7 @@ module Puma
|
|
105
102
|
write_pid
|
106
103
|
|
107
104
|
path = @options[:state]
|
105
|
+
permission = @options[:state_permission]
|
108
106
|
return unless path
|
109
107
|
|
110
108
|
require 'puma/state_file'
|
@@ -113,8 +111,9 @@ module Puma
|
|
113
111
|
sf.pid = Process.pid
|
114
112
|
sf.control_url = @options[:control_url]
|
115
113
|
sf.control_auth_token = @options[:control_auth_token]
|
114
|
+
sf.running_from = File.expand_path('.')
|
116
115
|
|
117
|
-
sf.save path
|
116
|
+
sf.save path, permission
|
118
117
|
end
|
119
118
|
|
120
119
|
# Delete the configured pidfile
|
@@ -174,22 +173,24 @@ module Puma
|
|
174
173
|
case @status
|
175
174
|
when :halt
|
176
175
|
log "* Stopping immediately!"
|
176
|
+
@runner.stop_control
|
177
177
|
when :run, :stop
|
178
178
|
graceful_stop
|
179
179
|
when :restart
|
180
180
|
log "* Restarting..."
|
181
181
|
ENV.replace(previous_env)
|
182
|
-
@runner.
|
182
|
+
@runner.stop_control
|
183
183
|
restart!
|
184
184
|
when :exit
|
185
185
|
# nothing
|
186
186
|
end
|
187
|
-
@
|
187
|
+
close_binder_listeners unless @status == :restart
|
188
188
|
end
|
189
189
|
|
190
|
-
# Return
|
191
|
-
|
192
|
-
|
190
|
+
# Return all tcp ports the launcher may be using, TCP or SSL
|
191
|
+
# @version 5.0.0
|
192
|
+
def connected_ports
|
193
|
+
@binder.connected_ports
|
193
194
|
end
|
194
195
|
|
195
196
|
def restart_args
|
@@ -202,9 +203,22 @@ module Puma
|
|
202
203
|
end
|
203
204
|
|
204
205
|
def close_binder_listeners
|
206
|
+
@runner.close_control_listeners
|
205
207
|
@binder.close_listeners
|
206
208
|
end
|
207
209
|
|
210
|
+
# @version 5.0.0
|
211
|
+
def thread_status
|
212
|
+
Thread.list.each do |thread|
|
213
|
+
name = "Thread: TID-#{thread.object_id.to_s(36)}"
|
214
|
+
name += " #{thread['label']}" if thread['label']
|
215
|
+
name += " #{thread.name}" if thread.respond_to?(:name) && thread.name
|
216
|
+
backtrace = thread.backtrace || ["<no backtrace available>"]
|
217
|
+
|
218
|
+
yield name, backtrace
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
208
222
|
private
|
209
223
|
|
210
224
|
# If configured, write the pid of the current process out
|
@@ -225,7 +239,7 @@ module Puma
|
|
225
239
|
end
|
226
240
|
|
227
241
|
def restart!
|
228
|
-
@config.run_hooks :on_restart, self
|
242
|
+
@config.run_hooks :on_restart, self, @events
|
229
243
|
|
230
244
|
if Puma.jruby?
|
231
245
|
close_binder_listeners
|
@@ -241,6 +255,7 @@ module Puma
|
|
241
255
|
else
|
242
256
|
argv = restart_args
|
243
257
|
Dir.chdir(@restart_dir)
|
258
|
+
ENV.update(@binder.redirects_for_restart_env)
|
244
259
|
argv += [@binder.redirects_for_restart]
|
245
260
|
Kernel.exec(*argv)
|
246
261
|
end
|
@@ -275,6 +290,7 @@ module Puma
|
|
275
290
|
end
|
276
291
|
|
277
292
|
def prune_bundler
|
293
|
+
return if ENV['PUMA_BUNDLER_PRUNED']
|
278
294
|
return unless defined?(Bundler)
|
279
295
|
require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
|
280
296
|
unless puma_wild_location
|
@@ -286,8 +302,10 @@ module Puma
|
|
286
302
|
|
287
303
|
log '* Pruning Bundler environment'
|
288
304
|
home = ENV['GEM_HOME']
|
289
|
-
Bundler.
|
305
|
+
bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
|
306
|
+
with_unbundled_env do
|
290
307
|
ENV['GEM_HOME'] = home
|
308
|
+
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
291
309
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
292
310
|
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
293
311
|
# Ruby 2.0+ defaults to true which breaks socket activation
|
@@ -323,21 +341,6 @@ module Puma
|
|
323
341
|
log "- Goodbye!"
|
324
342
|
end
|
325
343
|
|
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
344
|
def set_process_title
|
342
345
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
343
346
|
end
|
@@ -456,8 +459,13 @@ module Puma
|
|
456
459
|
end
|
457
460
|
|
458
461
|
begin
|
459
|
-
|
460
|
-
|
462
|
+
unless Puma.jruby? # INFO in use by JVM already
|
463
|
+
Signal.trap "SIGINFO" do
|
464
|
+
thread_status do |name, backtrace|
|
465
|
+
@events.log name
|
466
|
+
@events.log backtrace.map { |bt| " #{bt}" }
|
467
|
+
end
|
468
|
+
end
|
461
469
|
end
|
462
470
|
rescue Exception
|
463
471
|
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
|
@@ -471,5 +479,15 @@ module Puma
|
|
471
479
|
raise "#{feature} is not supported on your version of RubyGems. " \
|
472
480
|
"You must have RubyGems #{min_version}+ to use this feature."
|
473
481
|
end
|
482
|
+
|
483
|
+
# @version 5.0.0
|
484
|
+
def with_unbundled_env
|
485
|
+
bundler_ver = Gem::Version.new(Bundler::VERSION)
|
486
|
+
if bundler_ver < Gem::Version.new('2.1.0')
|
487
|
+
Bundler.with_clean_env { yield }
|
488
|
+
else
|
489
|
+
Bundler.with_unbundled_env { yield }
|
490
|
+
end
|
491
|
+
end
|
474
492
|
end
|
475
493
|
end
|