puma 4.3.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 +1346 -518
- data/LICENSE +23 -20
- data/README.md +74 -31
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +24 -20
- data/docs/compile_options.md +19 -0
- data/docs/deployment.md +15 -10
- data/docs/fork_worker.md +33 -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 +2 -2
- data/docs/rails_dev_mode.md +29 -0
- data/docs/restart.md +46 -23
- data/docs/signals.md +7 -6
- data/docs/stats.md +142 -0
- data/docs/systemd.md +27 -67
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +22 -8
- data/ext/puma_http11/http11_parser.c +45 -47
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +1 -1
- data/ext/puma_http11/http11_parser.rl +1 -1
- data/ext/puma_http11/mini_ssl.c +211 -118
- 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/Http11Parser.java +5 -7
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
- data/ext/puma_http11/puma_http11.c +31 -50
- data/lib/puma.rb +46 -0
- data/lib/puma/app/status.rb +47 -36
- data/lib/puma/binder.rb +177 -103
- data/lib/puma/cli.rb +11 -15
- data/lib/puma/client.rb +73 -74
- data/lib/puma/cluster.rb +184 -198
- 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 +55 -49
- data/lib/puma/const.rb +13 -5
- data/lib/puma/control_cli.rb +93 -76
- data/lib/puma/detect.rb +24 -3
- data/lib/puma/dsl.rb +266 -92
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +55 -34
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/json.rb +96 -0
- data/lib/puma/launcher.rb +113 -45
- data/lib/puma/minissl.rb +114 -33
- data/lib/puma/minissl/context_builder.rb +6 -3
- data/lib/puma/null_io.rb +13 -1
- data/lib/puma/plugin.rb +1 -10
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +0 -4
- data/lib/puma/reactor.rb +85 -369
- data/lib/puma/request.rb +467 -0
- data/lib/puma/runner.rb +29 -58
- data/lib/puma/server.rb +267 -729
- data/lib/puma/single.rb +9 -65
- data/lib/puma/state_file.rb +8 -3
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +119 -53
- data/lib/puma/util.rb +12 -0
- data/lib/rack/handler/puma.rb +2 -3
- data/tools/{docker/Dockerfile → Dockerfile} +0 -0
- metadata +25 -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/accept_nonblock.rb +0 -29
- 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,53 +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
|
-
@
|
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
|
-
#
|
108
|
+
# @param error <Puma::MiniSSL::SSLError>
|
109
|
+
# @param ssl_socket <Puma::MiniSSL::Socket>
|
105
110
|
#
|
106
|
-
def ssl_error(
|
111
|
+
def ssl_error(error, ssl_socket)
|
112
|
+
peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
|
113
|
+
peercert = ssl_socket.peercert
|
107
114
|
subject = peercert ? peercert.subject : nil
|
108
|
-
@
|
115
|
+
@error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
|
109
116
|
end
|
110
117
|
|
111
118
|
# An unknown error has occurred.
|
112
|
-
# +
|
113
|
-
# +
|
119
|
+
# +error+ an exception object, +req+ the request,
|
120
|
+
# and +text+ additional info
|
114
121
|
#
|
115
|
-
def unknown_error(
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
@stderr.puts string_block.join("\n")
|
127
|
-
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)
|
128
133
|
end
|
129
134
|
|
130
135
|
def on_booted(&block)
|
131
136
|
register(:on_booted, &block)
|
132
137
|
end
|
133
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
|
+
|
134
147
|
def fire_on_booted!
|
135
148
|
fire(:on_booted)
|
136
149
|
end
|
137
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
|
+
|
138
159
|
DEFAULT = new(STDOUT, STDERR)
|
139
160
|
|
140
161
|
# Returns an Events object which writes its status to 2 StringIO
|
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/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
@@ -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
|
|
@@ -57,6 +58,13 @@ module Puma
|
|
57
58
|
|
58
59
|
@config.load
|
59
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
|
+
|
60
68
|
@options = @config.options
|
61
69
|
@config.clamp
|
62
70
|
|
@@ -69,10 +77,6 @@ module Puma
|
|
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?
|
@@ -90,6 +94,8 @@ module Puma
|
|
90
94
|
Puma.stats_object = @runner
|
91
95
|
|
92
96
|
@status = :run
|
97
|
+
|
98
|
+
log_config if ENV['PUMA_LOG_CONFIG']
|
93
99
|
end
|
94
100
|
|
95
101
|
attr_reader :binder, :events, :config, :options, :restart_dir
|
@@ -105,6 +111,7 @@ module Puma
|
|
105
111
|
write_pid
|
106
112
|
|
107
113
|
path = @options[:state]
|
114
|
+
permission = @options[:state_permission]
|
108
115
|
return unless path
|
109
116
|
|
110
117
|
require 'puma/state_file'
|
@@ -113,8 +120,9 @@ module Puma
|
|
113
120
|
sf.pid = Process.pid
|
114
121
|
sf.control_url = @options[:control_url]
|
115
122
|
sf.control_auth_token = @options[:control_auth_token]
|
123
|
+
sf.running_from = File.expand_path('.')
|
116
124
|
|
117
|
-
sf.save path
|
125
|
+
sf.save path, permission
|
118
126
|
end
|
119
127
|
|
120
128
|
# Delete the configured pidfile
|
@@ -169,29 +177,34 @@ module Puma
|
|
169
177
|
|
170
178
|
setup_signals
|
171
179
|
set_process_title
|
180
|
+
integrate_with_systemd
|
172
181
|
@runner.run
|
173
182
|
|
174
183
|
case @status
|
175
184
|
when :halt
|
176
185
|
log "* Stopping immediately!"
|
186
|
+
@runner.stop_control
|
177
187
|
when :run, :stop
|
178
188
|
graceful_stop
|
179
189
|
when :restart
|
180
190
|
log "* Restarting..."
|
181
191
|
ENV.replace(previous_env)
|
182
|
-
@runner.
|
192
|
+
@runner.stop_control
|
183
193
|
restart!
|
184
194
|
when :exit
|
185
195
|
# nothing
|
186
196
|
end
|
187
|
-
@
|
197
|
+
close_binder_listeners unless @status == :restart
|
188
198
|
end
|
189
199
|
|
190
|
-
# Return
|
191
|
-
|
192
|
-
|
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
|
193
205
|
end
|
194
206
|
|
207
|
+
# @!attribute [r] restart_args
|
195
208
|
def restart_args
|
196
209
|
cmd = @options[:restart_cmd]
|
197
210
|
if cmd
|
@@ -202,7 +215,25 @@ module Puma
|
|
202
215
|
end
|
203
216
|
|
204
217
|
def close_binder_listeners
|
218
|
+
@runner.close_control_listeners
|
205
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
|
206
237
|
end
|
207
238
|
|
208
239
|
private
|
@@ -212,11 +243,10 @@ module Puma
|
|
212
243
|
def write_pid
|
213
244
|
path = @options[:pidfile]
|
214
245
|
return unless path
|
215
|
-
|
216
|
-
File.
|
217
|
-
cur = Process.pid
|
246
|
+
cur_pid = Process.pid
|
247
|
+
File.write path, cur_pid, mode: 'wb:UTF-8'
|
218
248
|
at_exit do
|
219
|
-
delete_pidfile if
|
249
|
+
delete_pidfile if cur_pid == Process.pid
|
220
250
|
end
|
221
251
|
end
|
222
252
|
|
@@ -225,7 +255,8 @@ module Puma
|
|
225
255
|
end
|
226
256
|
|
227
257
|
def restart!
|
228
|
-
@
|
258
|
+
@events.fire_on_restart!
|
259
|
+
@config.run_hooks :on_restart, self, @events
|
229
260
|
|
230
261
|
if Puma.jruby?
|
231
262
|
close_binder_listeners
|
@@ -241,21 +272,20 @@ module Puma
|
|
241
272
|
else
|
242
273
|
argv = restart_args
|
243
274
|
Dir.chdir(@restart_dir)
|
275
|
+
ENV.update(@binder.redirects_for_restart_env)
|
244
276
|
argv += [@binder.redirects_for_restart]
|
245
277
|
Kernel.exec(*argv)
|
246
278
|
end
|
247
279
|
end
|
248
280
|
|
249
|
-
|
281
|
+
# @!attribute [r] files_to_require_after_prune
|
282
|
+
def files_to_require_after_prune
|
250
283
|
puma = spec_for_gem("puma")
|
251
284
|
|
252
|
-
|
253
|
-
"#{d.name}:#{spec_for_gem(d.name).version}"
|
254
|
-
end
|
255
|
-
|
256
|
-
[deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
|
285
|
+
require_paths_for_gem(puma) + extra_runtime_deps_directories
|
257
286
|
end
|
258
287
|
|
288
|
+
# @!attribute [r] extra_runtime_deps_directories
|
259
289
|
def extra_runtime_deps_directories
|
260
290
|
Array(@options[:extra_runtime_dependencies]).map do |d_name|
|
261
291
|
if (spec = spec_for_gem(d_name))
|
@@ -267,6 +297,7 @@ module Puma
|
|
267
297
|
end.flatten.compact
|
268
298
|
end
|
269
299
|
|
300
|
+
# @!attribute [r] puma_wild_location
|
270
301
|
def puma_wild_location
|
271
302
|
puma = spec_for_gem("puma")
|
272
303
|
dirs = require_paths_for_gem(puma)
|
@@ -275,6 +306,7 @@ module Puma
|
|
275
306
|
end
|
276
307
|
|
277
308
|
def prune_bundler
|
309
|
+
return if ENV['PUMA_BUNDLER_PRUNED']
|
278
310
|
return unless defined?(Bundler)
|
279
311
|
require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
|
280
312
|
unless puma_wild_location
|
@@ -282,20 +314,46 @@ module Puma
|
|
282
314
|
return
|
283
315
|
end
|
284
316
|
|
285
|
-
|
317
|
+
dirs = files_to_require_after_prune
|
286
318
|
|
287
319
|
log '* Pruning Bundler environment'
|
288
320
|
home = ENV['GEM_HOME']
|
289
|
-
Bundler.
|
321
|
+
bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
|
322
|
+
with_unbundled_env do
|
290
323
|
ENV['GEM_HOME'] = home
|
324
|
+
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
291
325
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
292
|
-
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')
|
326
|
+
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')] + @original_argv
|
293
327
|
# Ruby 2.0+ defaults to true which breaks socket activation
|
294
328
|
args += [{:close_others => false}]
|
295
329
|
Kernel.exec(*args)
|
296
330
|
end
|
297
331
|
end
|
298
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
|
+
|
299
357
|
def spec_for_gem(gem_name)
|
300
358
|
Bundler.rubygems.loaded_specs(gem_name)
|
301
359
|
end
|
@@ -318,30 +376,15 @@ module Puma
|
|
318
376
|
end
|
319
377
|
|
320
378
|
def graceful_stop
|
379
|
+
@events.fire_on_stopped!
|
321
380
|
@runner.stop_blocked
|
322
|
-
log "=== puma shutdown: #{Time.now} ==="
|
323
|
-
log "- Goodbye!"
|
324
|
-
end
|
325
|
-
|
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
381
|
end
|
340
382
|
|
341
383
|
def set_process_title
|
342
384
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
343
385
|
end
|
344
386
|
|
387
|
+
# @!attribute [r] title
|
345
388
|
def title
|
346
389
|
buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
|
347
390
|
buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
@@ -353,6 +396,7 @@ module Puma
|
|
353
396
|
ENV['RACK_ENV'] = environment
|
354
397
|
end
|
355
398
|
|
399
|
+
# @!attribute [r] environment
|
356
400
|
def environment
|
357
401
|
@environment
|
358
402
|
end
|
@@ -456,8 +500,13 @@ module Puma
|
|
456
500
|
end
|
457
501
|
|
458
502
|
begin
|
459
|
-
|
460
|
-
|
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
|
461
510
|
end
|
462
511
|
rescue Exception
|
463
512
|
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
|
@@ -471,5 +520,24 @@ module Puma
|
|
471
520
|
raise "#{feature} is not supported on your version of RubyGems. " \
|
472
521
|
"You must have RubyGems #{min_version}+ to use this feature."
|
473
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"
|
541
|
+
end
|
474
542
|
end
|
475
543
|
end
|