puma 3.12.6 → 5.3.2
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 +1400 -451
- data/LICENSE +23 -20
- data/README.md +131 -60
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +24 -19
- data/docs/compile_options.md +19 -0
- data/docs/deployment.md +38 -13
- data/docs/fork_worker.md +33 -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 +1 -1
- data/docs/plugins.md +20 -10
- data/docs/rails_dev_mode.md +29 -0
- data/docs/restart.md +47 -22
- data/docs/signals.md +7 -6
- data/docs/stats.md +142 -0
- data/docs/systemd.md +48 -70
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +27 -0
- data/ext/puma_http11/http11_parser.c +81 -108
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +22 -38
- data/ext/puma_http11/http11_parser.rl +1 -1
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +254 -91
- 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 +89 -106
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
- data/ext/puma_http11/puma_http11.c +34 -50
- data/lib/puma.rb +54 -0
- data/lib/puma/app/status.rb +68 -49
- data/lib/puma/binder.rb +191 -139
- data/lib/puma/cli.rb +15 -15
- data/lib/puma/client.rb +247 -226
- data/lib/puma/cluster.rb +221 -212
- data/lib/puma/cluster/worker.rb +183 -0
- data/lib/puma/cluster/worker_handle.rb +90 -0
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +58 -51
- data/lib/puma/const.rb +32 -20
- data/lib/puma/control_cli.rb +109 -67
- data/lib/puma/detect.rb +24 -3
- data/lib/puma/dsl.rb +519 -121
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +55 -31
- data/lib/puma/io_buffer.rb +7 -5
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/json.rb +96 -0
- data/lib/puma/launcher.rb +178 -68
- data/lib/puma/minissl.rb +147 -48
- data/lib/puma/minissl/context_builder.rb +79 -0
- data/lib/puma/null_io.rb +13 -1
- data/lib/puma/plugin.rb +6 -12
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +2 -4
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +85 -316
- data/lib/puma/request.rb +467 -0
- data/lib/puma/runner.rb +31 -52
- data/lib/puma/server.rb +275 -726
- data/lib/puma/single.rb +11 -67
- data/lib/puma/state_file.rb +8 -3
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +129 -81
- data/lib/puma/util.rb +13 -6
- data/lib/rack/handler/puma.rb +5 -6
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +45 -28
- 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
@@ -0,0 +1,104 @@
|
|
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
|
+
|
19
|
+
@debug = ENV.key? 'PUMA_DEBUG'
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.stdio
|
23
|
+
new $stderr
|
24
|
+
end
|
25
|
+
|
26
|
+
# Print occurred 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
|
+
log title(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Print occurred 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 request_parsed?(req)
|
54
|
+
string_block << error.backtrace if error
|
55
|
+
|
56
|
+
log 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
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def log(str)
|
99
|
+
ioerr.puts str
|
100
|
+
|
101
|
+
ioerr.flush unless ioerr.sync
|
102
|
+
end
|
103
|
+
end
|
104
|
+
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)
|
@@ -32,10 +30,8 @@ module Puma
|
|
32
30
|
@stdout = stdout
|
33
31
|
@stderr = stderr
|
34
32
|
|
35
|
-
@stdout.sync = true
|
36
|
-
@stderr.sync = true
|
37
|
-
|
38
33
|
@debug = ENV.key? 'PUMA_DEBUG'
|
34
|
+
@error_logger = ErrorLogger.new(@stderr)
|
39
35
|
|
40
36
|
@hooks = Hash.new { |h,k| h[k] = [] }
|
41
37
|
end
|
@@ -66,7 +62,10 @@ module Puma
|
|
66
62
|
# Write +str+ to +@stdout+
|
67
63
|
#
|
68
64
|
def log(str)
|
69
|
-
@stdout.puts format(str)
|
65
|
+
@stdout.puts format(str) if @stdout.respond_to? :puts
|
66
|
+
|
67
|
+
@stdout.flush unless @stdout.sync
|
68
|
+
rescue Errno::EPIPE
|
70
69
|
end
|
71
70
|
|
72
71
|
def write(str)
|
@@ -80,7 +79,7 @@ module Puma
|
|
80
79
|
# Write +str+ to +@stderr+
|
81
80
|
#
|
82
81
|
def error(str)
|
83
|
-
@
|
82
|
+
@error_logger.info(text: format("ERROR: #{str}"))
|
84
83
|
exit 1
|
85
84
|
end
|
86
85
|
|
@@ -88,50 +87,75 @@ module Puma
|
|
88
87
|
formatter.call(str)
|
89
88
|
end
|
90
89
|
|
90
|
+
# An HTTP connection error has occurred.
|
91
|
+
# +error+ a connection exception, +req+ the request,
|
92
|
+
# and +text+ additional info
|
93
|
+
# @version 5.0.0
|
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
|
-
@
|
103
|
+
def parse_error(error, req)
|
104
|
+
@error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
|
97
105
|
end
|
98
106
|
|
99
107
|
# An SSL error has occurred.
|
100
|
-
#
|
101
|
-
#
|
108
|
+
# @param error <Puma::MiniSSL::SSLError>
|
109
|
+
# @param ssl_socket <Puma::MiniSSL::Socket>
|
102
110
|
#
|
103
|
-
def ssl_error(
|
111
|
+
def ssl_error(error, ssl_socket)
|
112
|
+
peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
|
113
|
+
peercert = ssl_socket.peercert
|
104
114
|
subject = peercert ? peercert.subject : nil
|
105
|
-
@
|
115
|
+
@error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
|
106
116
|
end
|
107
117
|
|
108
118
|
# An unknown error has occurred.
|
109
|
-
# +
|
110
|
-
# +
|
119
|
+
# +error+ an exception object, +req+ the request,
|
120
|
+
# and +text+ additional info
|
111
121
|
#
|
112
|
-
def unknown_error(
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
@stderr.puts string_block.join("\n")
|
124
|
-
end
|
122
|
+
def unknown_error(error, req=nil, text="Unknown error")
|
123
|
+
@error_logger.info(error: error, req: req, text: text)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Log occurred error debug dump.
|
127
|
+
# +error+ an exception object, +req+ the request,
|
128
|
+
# and +text+ additional info
|
129
|
+
# @version 5.0.0
|
130
|
+
#
|
131
|
+
def debug_error(error, req=nil, text="")
|
132
|
+
@error_logger.debug(error: error, req: req, text: text)
|
125
133
|
end
|
126
134
|
|
127
135
|
def on_booted(&block)
|
128
136
|
register(:on_booted, &block)
|
129
137
|
end
|
130
138
|
|
139
|
+
def on_restart(&block)
|
140
|
+
register(:on_restart, &block)
|
141
|
+
end
|
142
|
+
|
143
|
+
def on_stopped(&block)
|
144
|
+
register(:on_stopped, &block)
|
145
|
+
end
|
146
|
+
|
131
147
|
def fire_on_booted!
|
132
148
|
fire(:on_booted)
|
133
149
|
end
|
134
150
|
|
151
|
+
def fire_on_restart!
|
152
|
+
fire(:on_restart)
|
153
|
+
end
|
154
|
+
|
155
|
+
def fire_on_stopped!
|
156
|
+
fire(:on_stopped)
|
157
|
+
end
|
158
|
+
|
135
159
|
DEFAULT = new(STDOUT, STDERR)
|
136
160
|
|
137
161
|
# Returns an Events object which writes its status to 2 StringIO
|
data/lib/puma/io_buffer.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
module Puma
|
4
|
+
class IOBuffer < String
|
5
|
+
def append(*args)
|
6
|
+
args.each { |a| concat(a) }
|
7
|
+
end
|
4
8
|
|
5
|
-
|
6
|
-
|
7
|
-
else
|
8
|
-
require 'puma/puma_http11'
|
9
|
+
alias reset clear
|
10
|
+
end
|
9
11
|
end
|
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/json.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module Puma
|
5
|
+
|
6
|
+
# Puma deliberately avoids the use of the json gem and instead performs JSON
|
7
|
+
# serialization without any external dependencies. In a puma cluster, loading
|
8
|
+
# any gem into the puma master process means that operators cannot use a
|
9
|
+
# phased restart to upgrade their application if the new version of that
|
10
|
+
# application uses a different version of that gem. The json gem in
|
11
|
+
# particular is additionally problematic because it leverages native
|
12
|
+
# extensions. If the puma master process relies on a gem with native
|
13
|
+
# extensions and operators remove gems from disk related to old releases,
|
14
|
+
# subsequent phased restarts can fail.
|
15
|
+
#
|
16
|
+
# The implementation of JSON serialization in this module is not designed to
|
17
|
+
# be particularly full-featured or fast. It just has to handle the few places
|
18
|
+
# where Puma relies on JSON serialization internally.
|
19
|
+
|
20
|
+
module JSON
|
21
|
+
QUOTE = /"/
|
22
|
+
BACKSLASH = /\\/
|
23
|
+
CONTROL_CHAR_TO_ESCAPE = /[\x00-\x1F]/ # As required by ECMA-404
|
24
|
+
CHAR_TO_ESCAPE = Regexp.union QUOTE, BACKSLASH, CONTROL_CHAR_TO_ESCAPE
|
25
|
+
|
26
|
+
class SerializationError < StandardError; end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def generate(value)
|
30
|
+
StringIO.open do |io|
|
31
|
+
serialize_value io, value
|
32
|
+
io.string
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def serialize_value(output, value)
|
39
|
+
case value
|
40
|
+
when Hash
|
41
|
+
output << '{'
|
42
|
+
value.each_with_index do |(k, v), index|
|
43
|
+
output << ',' if index != 0
|
44
|
+
serialize_object_key output, k
|
45
|
+
output << ':'
|
46
|
+
serialize_value output, v
|
47
|
+
end
|
48
|
+
output << '}'
|
49
|
+
when Array
|
50
|
+
output << '['
|
51
|
+
value.each_with_index do |member, index|
|
52
|
+
output << ',' if index != 0
|
53
|
+
serialize_value output, member
|
54
|
+
end
|
55
|
+
output << ']'
|
56
|
+
when Integer, Float
|
57
|
+
output << value.to_s
|
58
|
+
when String
|
59
|
+
serialize_string output, value
|
60
|
+
when true
|
61
|
+
output << 'true'
|
62
|
+
when false
|
63
|
+
output << 'false'
|
64
|
+
when nil
|
65
|
+
output << 'null'
|
66
|
+
else
|
67
|
+
raise SerializationError, "Unexpected value of type #{value.class}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def serialize_string(output, value)
|
72
|
+
output << '"'
|
73
|
+
output << value.gsub(CHAR_TO_ESCAPE) do |character|
|
74
|
+
case character
|
75
|
+
when BACKSLASH
|
76
|
+
'\\\\'
|
77
|
+
when QUOTE
|
78
|
+
'\\"'
|
79
|
+
when CONTROL_CHAR_TO_ESCAPE
|
80
|
+
'\u%.4X' % character.ord
|
81
|
+
end
|
82
|
+
end
|
83
|
+
output << '"'
|
84
|
+
end
|
85
|
+
|
86
|
+
def serialize_object_key(output, value)
|
87
|
+
case value
|
88
|
+
when Symbol, String
|
89
|
+
serialize_string output, value.to_s
|
90
|
+
else
|
91
|
+
raise SerializationError, "Could not serialize object of type #{value.class} as object key"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/puma/launcher.rb
CHANGED
@@ -2,12 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'puma/events'
|
4
4
|
require 'puma/detect'
|
5
|
-
|
6
5
|
require 'puma/cluster'
|
7
6
|
require 'puma/single'
|
8
|
-
|
9
7
|
require 'puma/const'
|
10
|
-
|
11
8
|
require 'puma/binder'
|
12
9
|
|
13
10
|
module Puma
|
@@ -50,8 +47,9 @@ module Puma
|
|
50
47
|
@original_argv = @argv.dup
|
51
48
|
@config = conf
|
52
49
|
|
53
|
-
@binder = Binder.new(@events)
|
54
|
-
@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 }
|
55
53
|
|
56
54
|
@environment = conf.environment
|
57
55
|
|
@@ -60,19 +58,25 @@ module Puma
|
|
60
58
|
|
61
59
|
@config.load
|
62
60
|
|
61
|
+
if @config.options[:bind_to_activated_sockets]
|
62
|
+
@config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
|
63
|
+
@config.options[:binds],
|
64
|
+
@config.options[:bind_to_activated_sockets] == 'only'
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
63
68
|
@options = @config.options
|
64
69
|
@config.clamp
|
65
70
|
|
71
|
+
@events.formatter = Events::PidFormatter.new if clustered?
|
72
|
+
@events.formatter = options[:log_formatter] if @options[:log_formatter]
|
73
|
+
|
66
74
|
generate_restart_data
|
67
75
|
|
68
76
|
if clustered? && !Process.respond_to?(:fork)
|
69
77
|
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
|
70
78
|
end
|
71
79
|
|
72
|
-
if @options[:daemon] && Puma.windows?
|
73
|
-
unsupported 'daemon mode not supported on Windows'
|
74
|
-
end
|
75
|
-
|
76
80
|
Dir.chdir(@restart_dir)
|
77
81
|
|
78
82
|
prune_bundler if prune_bundler?
|
@@ -81,7 +85,6 @@ module Puma
|
|
81
85
|
set_rack_environment
|
82
86
|
|
83
87
|
if clustered?
|
84
|
-
@events.formatter = Events::PidFormatter.new
|
85
88
|
@options[:logger] = @events
|
86
89
|
|
87
90
|
@runner = Cluster.new(self, @events)
|
@@ -91,6 +94,8 @@ module Puma
|
|
91
94
|
Puma.stats_object = @runner
|
92
95
|
|
93
96
|
@status = :run
|
97
|
+
|
98
|
+
log_config if ENV['PUMA_LOG_CONFIG']
|
94
99
|
end
|
95
100
|
|
96
101
|
attr_reader :binder, :events, :config, :options, :restart_dir
|
@@ -106,6 +111,7 @@ module Puma
|
|
106
111
|
write_pid
|
107
112
|
|
108
113
|
path = @options[:state]
|
114
|
+
permission = @options[:state_permission]
|
109
115
|
return unless path
|
110
116
|
|
111
117
|
require 'puma/state_file'
|
@@ -114,8 +120,9 @@ module Puma
|
|
114
120
|
sf.pid = Process.pid
|
115
121
|
sf.control_url = @options[:control_url]
|
116
122
|
sf.control_auth_token = @options[:control_auth_token]
|
123
|
+
sf.running_from = File.expand_path('.')
|
117
124
|
|
118
|
-
sf.save path
|
125
|
+
sf.save path, permission
|
119
126
|
end
|
120
127
|
|
121
128
|
# Delete the configured pidfile
|
@@ -124,19 +131,6 @@ module Puma
|
|
124
131
|
File.unlink(path) if path && File.exist?(path)
|
125
132
|
end
|
126
133
|
|
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
134
|
# Begin async shutdown of the server
|
141
135
|
def halt
|
142
136
|
@status = :halt
|
@@ -183,28 +177,34 @@ module Puma
|
|
183
177
|
|
184
178
|
setup_signals
|
185
179
|
set_process_title
|
180
|
+
integrate_with_systemd
|
186
181
|
@runner.run
|
187
182
|
|
188
183
|
case @status
|
189
184
|
when :halt
|
190
185
|
log "* Stopping immediately!"
|
186
|
+
@runner.stop_control
|
191
187
|
when :run, :stop
|
192
188
|
graceful_stop
|
193
189
|
when :restart
|
194
190
|
log "* Restarting..."
|
195
191
|
ENV.replace(previous_env)
|
196
|
-
@runner.
|
192
|
+
@runner.stop_control
|
197
193
|
restart!
|
198
194
|
when :exit
|
199
195
|
# nothing
|
200
196
|
end
|
197
|
+
close_binder_listeners unless @status == :restart
|
201
198
|
end
|
202
199
|
|
203
|
-
# Return
|
204
|
-
|
205
|
-
|
200
|
+
# Return all tcp ports the launcher may be using, TCP or SSL
|
201
|
+
# @!attribute [r] connected_ports
|
202
|
+
# @version 5.0.0
|
203
|
+
def connected_ports
|
204
|
+
@binder.connected_ports
|
206
205
|
end
|
207
206
|
|
207
|
+
# @!attribute [r] restart_args
|
208
208
|
def restart_args
|
209
209
|
cmd = @options[:restart_cmd]
|
210
210
|
if cmd
|
@@ -214,14 +214,49 @@ module Puma
|
|
214
214
|
end
|
215
215
|
end
|
216
216
|
|
217
|
+
def close_binder_listeners
|
218
|
+
@runner.close_control_listeners
|
219
|
+
@binder.close_listeners
|
220
|
+
unless @status == :restart
|
221
|
+
log "=== puma shutdown: #{Time.now} ==="
|
222
|
+
log "- Goodbye!"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# @!attribute [r] thread_status
|
227
|
+
# @version 5.0.0
|
228
|
+
def thread_status
|
229
|
+
Thread.list.each do |thread|
|
230
|
+
name = "Thread: TID-#{thread.object_id.to_s(36)}"
|
231
|
+
name += " #{thread['label']}" if thread['label']
|
232
|
+
name += " #{thread.name}" if thread.respond_to?(:name) && thread.name
|
233
|
+
backtrace = thread.backtrace || ["<no backtrace available>"]
|
234
|
+
|
235
|
+
yield name, backtrace
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
217
239
|
private
|
218
240
|
|
241
|
+
# If configured, write the pid of the current process out
|
242
|
+
# to a file.
|
243
|
+
def write_pid
|
244
|
+
path = @options[:pidfile]
|
245
|
+
return unless path
|
246
|
+
cur_pid = Process.pid
|
247
|
+
File.write path, cur_pid, mode: 'wb:UTF-8'
|
248
|
+
at_exit do
|
249
|
+
delete_pidfile if cur_pid == Process.pid
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
219
253
|
def reload_worker_directory
|
220
254
|
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
221
255
|
end
|
222
256
|
|
223
257
|
def restart!
|
224
|
-
@
|
258
|
+
@events.fire_on_restart!
|
259
|
+
@config.run_hooks :on_restart, self, @events
|
225
260
|
|
226
261
|
if Puma.jruby?
|
227
262
|
close_binder_listeners
|
@@ -235,48 +270,98 @@ module Puma
|
|
235
270
|
Dir.chdir(@restart_dir)
|
236
271
|
Kernel.exec(*argv)
|
237
272
|
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
273
|
argv = restart_args
|
245
274
|
Dir.chdir(@restart_dir)
|
246
|
-
|
275
|
+
ENV.update(@binder.redirects_for_restart_env)
|
276
|
+
argv += [@binder.redirects_for_restart]
|
247
277
|
Kernel.exec(*argv)
|
248
278
|
end
|
249
279
|
end
|
250
280
|
|
251
|
-
|
252
|
-
|
253
|
-
puma =
|
254
|
-
|
281
|
+
# @!attribute [r] files_to_require_after_prune
|
282
|
+
def files_to_require_after_prune
|
283
|
+
puma = spec_for_gem("puma")
|
284
|
+
|
285
|
+
require_paths_for_gem(puma) + extra_runtime_deps_directories
|
286
|
+
end
|
287
|
+
|
288
|
+
# @!attribute [r] extra_runtime_deps_directories
|
289
|
+
def extra_runtime_deps_directories
|
290
|
+
Array(@options[:extra_runtime_dependencies]).map do |d_name|
|
291
|
+
if (spec = spec_for_gem(d_name))
|
292
|
+
require_paths_for_gem(spec)
|
293
|
+
else
|
294
|
+
log "* Could not load extra dependency: #{d_name}"
|
295
|
+
nil
|
296
|
+
end
|
297
|
+
end.flatten.compact
|
298
|
+
end
|
299
|
+
|
300
|
+
# @!attribute [r] puma_wild_location
|
301
|
+
def puma_wild_location
|
302
|
+
puma = spec_for_gem("puma")
|
303
|
+
dirs = require_paths_for_gem(puma)
|
255
304
|
puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
|
305
|
+
File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
|
306
|
+
end
|
256
307
|
|
257
|
-
|
308
|
+
def prune_bundler
|
309
|
+
return if ENV['PUMA_BUNDLER_PRUNED']
|
310
|
+
return unless defined?(Bundler)
|
311
|
+
require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
|
312
|
+
unless puma_wild_location
|
258
313
|
log "! Unable to prune Bundler environment, continuing"
|
259
314
|
return
|
260
315
|
end
|
261
316
|
|
262
|
-
|
263
|
-
spec = Bundler.rubygems.loaded_specs(d.name)
|
264
|
-
"#{d.name}:#{spec.version.to_s}"
|
265
|
-
end
|
317
|
+
dirs = files_to_require_after_prune
|
266
318
|
|
267
319
|
log '* Pruning Bundler environment'
|
268
320
|
home = ENV['GEM_HOME']
|
269
|
-
Bundler.
|
321
|
+
bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
|
322
|
+
with_unbundled_env do
|
270
323
|
ENV['GEM_HOME'] = home
|
324
|
+
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
271
325
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
272
|
-
|
273
|
-
args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
326
|
+
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')] + @original_argv
|
274
327
|
# Ruby 2.0+ defaults to true which breaks socket activation
|
275
|
-
args += [{:close_others => false}]
|
328
|
+
args += [{:close_others => false}]
|
276
329
|
Kernel.exec(*args)
|
277
330
|
end
|
278
331
|
end
|
279
332
|
|
333
|
+
#
|
334
|
+
# Puma's systemd integration allows Puma to inform systemd:
|
335
|
+
# 1. when it has successfully started
|
336
|
+
# 2. when it is starting shutdown
|
337
|
+
# 3. periodically for a liveness check with a watchdog thread
|
338
|
+
#
|
339
|
+
|
340
|
+
def integrate_with_systemd
|
341
|
+
return unless ENV["NOTIFY_SOCKET"]
|
342
|
+
|
343
|
+
begin
|
344
|
+
require 'puma/systemd'
|
345
|
+
rescue LoadError
|
346
|
+
log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
|
347
|
+
return
|
348
|
+
end
|
349
|
+
|
350
|
+
log "* Enabling systemd notification integration"
|
351
|
+
|
352
|
+
systemd = Systemd.new(@events)
|
353
|
+
systemd.hook_events
|
354
|
+
systemd.start_watchdog
|
355
|
+
end
|
356
|
+
|
357
|
+
def spec_for_gem(gem_name)
|
358
|
+
Bundler.rubygems.loaded_specs(gem_name)
|
359
|
+
end
|
360
|
+
|
361
|
+
def require_paths_for_gem(gem_spec)
|
362
|
+
gem_spec.full_require_paths
|
363
|
+
end
|
364
|
+
|
280
365
|
def log(str)
|
281
366
|
@events.log str
|
282
367
|
end
|
@@ -291,15 +376,15 @@ module Puma
|
|
291
376
|
end
|
292
377
|
|
293
378
|
def graceful_stop
|
379
|
+
@events.fire_on_stopped!
|
294
380
|
@runner.stop_blocked
|
295
|
-
log "=== puma shutdown: #{Time.now} ==="
|
296
|
-
log "- Goodbye!"
|
297
381
|
end
|
298
382
|
|
299
383
|
def set_process_title
|
300
384
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
301
385
|
end
|
302
386
|
|
387
|
+
# @!attribute [r] title
|
303
388
|
def title
|
304
389
|
buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
|
305
390
|
buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
@@ -311,6 +396,7 @@ module Puma
|
|
311
396
|
ENV['RACK_ENV'] = environment
|
312
397
|
end
|
313
398
|
|
399
|
+
# @!attribute [r] environment
|
314
400
|
def environment
|
315
401
|
@environment
|
316
402
|
end
|
@@ -319,16 +405,6 @@ module Puma
|
|
319
405
|
@options[:prune_bundler] && clustered? && !@options[:preload_app]
|
320
406
|
end
|
321
407
|
|
322
|
-
def close_binder_listeners
|
323
|
-
@binder.listeners.each do |l, io|
|
324
|
-
io.close
|
325
|
-
uri = URI.parse(l)
|
326
|
-
next unless uri.scheme == 'unix'
|
327
|
-
File.unlink("#{uri.host}#{uri.path}")
|
328
|
-
end
|
329
|
-
end
|
330
|
-
|
331
|
-
|
332
408
|
def generate_restart_data
|
333
409
|
if dir = @options[:directory]
|
334
410
|
@restart_dir = dir
|
@@ -397,7 +473,7 @@ module Puma
|
|
397
473
|
Signal.trap "SIGTERM" do
|
398
474
|
graceful_stop
|
399
475
|
|
400
|
-
raise
|
476
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
401
477
|
end
|
402
478
|
rescue Exception
|
403
479
|
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
|
@@ -405,12 +481,6 @@ module Puma
|
|
405
481
|
|
406
482
|
begin
|
407
483
|
Signal.trap "SIGINT" do
|
408
|
-
if Puma.jruby?
|
409
|
-
@status = :exit
|
410
|
-
graceful_stop
|
411
|
-
exit
|
412
|
-
end
|
413
|
-
|
414
484
|
stop
|
415
485
|
end
|
416
486
|
rescue Exception
|
@@ -428,6 +498,46 @@ module Puma
|
|
428
498
|
rescue Exception
|
429
499
|
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
|
430
500
|
end
|
501
|
+
|
502
|
+
begin
|
503
|
+
unless Puma.jruby? # INFO in use by JVM already
|
504
|
+
Signal.trap "SIGINFO" do
|
505
|
+
thread_status do |name, backtrace|
|
506
|
+
@events.log name
|
507
|
+
@events.log backtrace.map { |bt| " #{bt}" }
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
rescue Exception
|
512
|
+
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
|
513
|
+
# to see this constantly on Linux.
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
def require_rubygems_min_version!(min_version, feature)
|
518
|
+
return if min_version <= Gem::Version.new(Gem::VERSION)
|
519
|
+
|
520
|
+
raise "#{feature} is not supported on your version of RubyGems. " \
|
521
|
+
"You must have RubyGems #{min_version}+ to use this feature."
|
522
|
+
end
|
523
|
+
|
524
|
+
# @version 5.0.0
|
525
|
+
def with_unbundled_env
|
526
|
+
bundler_ver = Gem::Version.new(Bundler::VERSION)
|
527
|
+
if bundler_ver < Gem::Version.new('2.1.0')
|
528
|
+
Bundler.with_clean_env { yield }
|
529
|
+
else
|
530
|
+
Bundler.with_unbundled_env { yield }
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
def log_config
|
535
|
+
log "Configuration:"
|
536
|
+
|
537
|
+
@config.final_options
|
538
|
+
.each { |config_key, value| log "- #{config_key}: #{value}" }
|
539
|
+
|
540
|
+
log "\n"
|
431
541
|
end
|
432
542
|
end
|
433
543
|
end
|