puma 4.3.3-java → 5.0.0.beta2-java
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 +79 -8
- data/LICENSE +23 -20
- data/README.md +18 -12
- 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 +5 -4
- 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 +12 -2
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +37 -6
- data/ext/puma_http11/puma_http11.c +3 -38
- data/lib/puma.rb +5 -0
- data/lib/puma/app/status.rb +18 -3
- data/lib/puma/binder.rb +66 -63
- data/lib/puma/cli.rb +7 -15
- data/lib/puma/client.rb +64 -14
- data/lib/puma/cluster.rb +183 -74
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +30 -42
- data/lib/puma/const.rb +2 -3
- data/lib/puma/control_cli.rb +27 -17
- data/lib/puma/detect.rb +8 -0
- data/lib/puma/dsl.rb +72 -36
- data/lib/puma/error_logger.rb +96 -0
- data/lib/puma/events.rb +33 -31
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/launcher.rb +46 -31
- data/lib/puma/minissl.rb +47 -10
- data/lib/puma/null_io.rb +1 -1
- data/lib/puma/plugin.rb +1 -10
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/rack/builder.rb +0 -4
- data/lib/puma/reactor.rb +8 -3
- data/lib/puma/runner.rb +6 -35
- data/lib/puma/server.rb +138 -182
- data/lib/puma/single.rb +7 -64
- data/lib/puma/state_file.rb +6 -3
- data/lib/puma/thread_pool.rb +90 -49
- data/lib/rack/handler/puma.rb +1 -3
- data/tools/{docker/Dockerfile → Dockerfile} +0 -0
- metadata +18 -21
- 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,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'puma/const'
|
4
|
+
|
5
|
+
module Puma
|
6
|
+
# The implementation of a detailed error logging.
|
7
|
+
#
|
8
|
+
class ErrorLogger
|
9
|
+
include Const
|
10
|
+
|
11
|
+
attr_reader :ioerr
|
12
|
+
|
13
|
+
REQUEST_FORMAT = %{"%s %s%s" - (%s)}
|
14
|
+
|
15
|
+
def initialize(ioerr)
|
16
|
+
@ioerr = ioerr
|
17
|
+
@ioerr.sync = true
|
18
|
+
|
19
|
+
@debug = ENV.key? 'PUMA_DEBUG'
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.stdio
|
23
|
+
new $stderr
|
24
|
+
end
|
25
|
+
|
26
|
+
# Print occured error details.
|
27
|
+
# +options+ hash with additional options:
|
28
|
+
# - +error+ is an exception object
|
29
|
+
# - +req+ the http request
|
30
|
+
# - +text+ (default nil) custom string to print in title
|
31
|
+
# and before all remaining info.
|
32
|
+
#
|
33
|
+
def info(options={})
|
34
|
+
ioerr.puts title(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Print occured error details only if
|
38
|
+
# environment variable PUMA_DEBUG is defined.
|
39
|
+
# +options+ hash with additional options:
|
40
|
+
# - +error+ is an exception object
|
41
|
+
# - +req+ the http request
|
42
|
+
# - +text+ (default nil) custom string to print in title
|
43
|
+
# and before all remaining info.
|
44
|
+
#
|
45
|
+
def debug(options={})
|
46
|
+
return unless @debug
|
47
|
+
|
48
|
+
error = options[:error]
|
49
|
+
req = options[:req]
|
50
|
+
|
51
|
+
string_block = []
|
52
|
+
string_block << title(options)
|
53
|
+
string_block << request_dump(req) if req
|
54
|
+
string_block << error_backtrace(options) if error
|
55
|
+
|
56
|
+
ioerr.puts string_block.join("\n")
|
57
|
+
end
|
58
|
+
|
59
|
+
def title(options={})
|
60
|
+
text = options[:text]
|
61
|
+
req = options[:req]
|
62
|
+
error = options[:error]
|
63
|
+
|
64
|
+
string_block = ["#{Time.now}"]
|
65
|
+
string_block << " #{text}" if text
|
66
|
+
string_block << " (#{request_title(req)})" if request_parsed?(req)
|
67
|
+
string_block << ": #{error.inspect}" if error
|
68
|
+
string_block.join('')
|
69
|
+
end
|
70
|
+
|
71
|
+
def request_dump(req)
|
72
|
+
"Headers: #{request_headers(req)}\n" \
|
73
|
+
"Body: #{req.body}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def request_title(req)
|
77
|
+
env = req.env
|
78
|
+
|
79
|
+
REQUEST_FORMAT % [
|
80
|
+
env[REQUEST_METHOD],
|
81
|
+
env[REQUEST_PATH] || env[PATH_INFO],
|
82
|
+
env[QUERY_STRING] || "",
|
83
|
+
env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
|
84
|
+
]
|
85
|
+
end
|
86
|
+
|
87
|
+
def request_headers(req)
|
88
|
+
headers = req.env.select { |key, _| key.start_with?('HTTP_') }
|
89
|
+
headers.map { |key, value| [key[5..-1], value] }.to_h.inspect
|
90
|
+
end
|
91
|
+
|
92
|
+
def request_parsed?(req)
|
93
|
+
req && req.env[REQUEST_METHOD]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
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,45 @@ 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
|
+
#
|
95
|
+
def connection_error(error, req, text="HTTP connection error")
|
96
|
+
@error_logger.info(error: error, req: req, text: text)
|
97
|
+
end
|
98
|
+
|
91
99
|
# An HTTP parse error has occurred.
|
92
|
-
# +
|
93
|
-
#
|
100
|
+
# +error+ a parsing exception,
|
101
|
+
# and +req+ the request.
|
94
102
|
#
|
95
|
-
def parse_error(
|
96
|
-
@
|
97
|
-
"(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \
|
98
|
-
"#{error.inspect}" \
|
99
|
-
"\n---\n"
|
103
|
+
def parse_error(error, req)
|
104
|
+
@error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
|
100
105
|
end
|
101
106
|
|
102
107
|
# An SSL error has occurred.
|
103
|
-
# +
|
104
|
-
# any peer certificate (if present)
|
108
|
+
# +error+ an exception object, +peeraddr+ peer address,
|
109
|
+
# and +peercert+ any peer certificate (if present).
|
105
110
|
#
|
106
|
-
def ssl_error(
|
111
|
+
def ssl_error(error, peeraddr, peercert)
|
107
112
|
subject = peercert ? peercert.subject : nil
|
108
|
-
@
|
113
|
+
@error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
|
109
114
|
end
|
110
115
|
|
111
116
|
# An unknown error has occurred.
|
112
|
-
# +
|
113
|
-
# +
|
117
|
+
# +error+ an exception object, +req+ the request,
|
118
|
+
# and +text+ additional info
|
114
119
|
#
|
115
|
-
def unknown_error(
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
string_block << error.backtrace
|
126
|
-
@stderr.puts string_block.join("\n")
|
127
|
-
end
|
120
|
+
def unknown_error(error, req=nil, text="Unknown error")
|
121
|
+
@error_logger.info(error: error, req: req, text: text)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Log occurred error debug dump.
|
125
|
+
# +error+ an exception object, +req+ the request,
|
126
|
+
# and +text+ additional info
|
127
|
+
#
|
128
|
+
def debug_error(error, req=nil, text="")
|
129
|
+
@error_logger.debug(error: error, req: req, text: text)
|
128
130
|
end
|
129
131
|
|
130
132
|
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,23 @@ 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
|
-
def
|
192
|
-
@binder.
|
190
|
+
# Return all tcp ports the launcher may be using, TCP or SSL
|
191
|
+
def connected_ports
|
192
|
+
@binder.connected_ports
|
193
193
|
end
|
194
194
|
|
195
195
|
def restart_args
|
@@ -202,9 +202,21 @@ module Puma
|
|
202
202
|
end
|
203
203
|
|
204
204
|
def close_binder_listeners
|
205
|
+
@runner.close_control_listeners
|
205
206
|
@binder.close_listeners
|
206
207
|
end
|
207
208
|
|
209
|
+
def thread_status
|
210
|
+
Thread.list.each do |thread|
|
211
|
+
name = "Thread: TID-#{thread.object_id.to_s(36)}"
|
212
|
+
name += " #{thread['label']}" if thread['label']
|
213
|
+
name += " #{thread.name}" if thread.respond_to?(:name) && thread.name
|
214
|
+
backtrace = thread.backtrace || ["<no backtrace available>"]
|
215
|
+
|
216
|
+
yield name, backtrace
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
208
220
|
private
|
209
221
|
|
210
222
|
# If configured, write the pid of the current process out
|
@@ -225,7 +237,7 @@ module Puma
|
|
225
237
|
end
|
226
238
|
|
227
239
|
def restart!
|
228
|
-
@config.run_hooks :on_restart, self
|
240
|
+
@config.run_hooks :on_restart, self, @events
|
229
241
|
|
230
242
|
if Puma.jruby?
|
231
243
|
close_binder_listeners
|
@@ -241,6 +253,7 @@ module Puma
|
|
241
253
|
else
|
242
254
|
argv = restart_args
|
243
255
|
Dir.chdir(@restart_dir)
|
256
|
+
ENV.update(@binder.redirects_for_restart_env)
|
244
257
|
argv += [@binder.redirects_for_restart]
|
245
258
|
Kernel.exec(*argv)
|
246
259
|
end
|
@@ -275,6 +288,7 @@ module Puma
|
|
275
288
|
end
|
276
289
|
|
277
290
|
def prune_bundler
|
291
|
+
return if ENV['PUMA_BUNDLER_PRUNED']
|
278
292
|
return unless defined?(Bundler)
|
279
293
|
require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
|
280
294
|
unless puma_wild_location
|
@@ -286,8 +300,10 @@ module Puma
|
|
286
300
|
|
287
301
|
log '* Pruning Bundler environment'
|
288
302
|
home = ENV['GEM_HOME']
|
289
|
-
Bundler.
|
303
|
+
bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
|
304
|
+
with_unbundled_env do
|
290
305
|
ENV['GEM_HOME'] = home
|
306
|
+
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
291
307
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
292
308
|
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
293
309
|
# Ruby 2.0+ defaults to true which breaks socket activation
|
@@ -323,21 +339,6 @@ module Puma
|
|
323
339
|
log "- Goodbye!"
|
324
340
|
end
|
325
341
|
|
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
342
|
def set_process_title
|
342
343
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
343
344
|
end
|
@@ -456,8 +457,13 @@ module Puma
|
|
456
457
|
end
|
457
458
|
|
458
459
|
begin
|
459
|
-
|
460
|
-
|
460
|
+
unless Puma.jruby? # INFO in use by JVM already
|
461
|
+
Signal.trap "SIGINFO" do
|
462
|
+
thread_status do |name, backtrace|
|
463
|
+
@events.log name
|
464
|
+
@events.log backtrace.map { |bt| " #{bt}" }
|
465
|
+
end
|
466
|
+
end
|
461
467
|
end
|
462
468
|
rescue Exception
|
463
469
|
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
|
@@ -471,5 +477,14 @@ module Puma
|
|
471
477
|
raise "#{feature} is not supported on your version of RubyGems. " \
|
472
478
|
"You must have RubyGems #{min_version}+ to use this feature."
|
473
479
|
end
|
480
|
+
|
481
|
+
def with_unbundled_env
|
482
|
+
bundler_ver = Gem::Version.new(Bundler::VERSION)
|
483
|
+
if bundler_ver < Gem::Version.new('2.1.0')
|
484
|
+
Bundler.with_clean_env { yield }
|
485
|
+
else
|
486
|
+
Bundler.with_unbundled_env { yield }
|
487
|
+
end
|
488
|
+
end
|
474
489
|
end
|
475
490
|
end
|