puma 5.0.4 → 5.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +322 -48
- data/LICENSE +0 -0
- data/README.md +95 -24
- data/bin/puma-wild +0 -0
- data/docs/architecture.md +57 -20
- data/docs/compile_options.md +21 -0
- data/docs/deployment.md +53 -67
- data/docs/fork_worker.md +2 -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 +0 -0
- data/docs/jungle/rc.d/README.md +1 -1
- data/docs/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +0 -0
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +7 -7
- data/docs/signals.md +11 -10
- data/docs/stats.md +142 -0
- data/docs/systemd.md +85 -66
- data/ext/puma_http11/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/ext_help.h +0 -0
- data/ext/puma_http11/extconf.rb +42 -6
- data/ext/puma_http11/http11_parser.c +68 -57
- 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/http11_parser_common.rl +1 -1
- data/ext/puma_http11/mini_ssl.c +226 -88
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +51 -51
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +28 -43
- data/ext/puma_http11/puma_http11.c +9 -3
- data/lib/puma/app/status.rb +4 -7
- data/lib/puma/binder.rb +138 -49
- data/lib/puma/cli.rb +18 -4
- data/lib/puma/client.rb +113 -31
- data/lib/puma/cluster/worker.rb +22 -19
- data/lib/puma/cluster/worker_handle.rb +13 -2
- data/lib/puma/cluster.rb +75 -33
- data/lib/puma/commonlogger.rb +0 -0
- data/lib/puma/configuration.rb +21 -2
- data/lib/puma/const.rb +17 -8
- data/lib/puma/control_cli.rb +76 -71
- data/lib/puma/detect.rb +19 -9
- data/lib/puma/dsl.rb +225 -31
- data/lib/puma/error_logger.rb +12 -5
- data/lib/puma/events.rb +18 -3
- data/lib/puma/io_buffer.rb +0 -0
- data/lib/puma/jruby_restart.rb +0 -0
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher.rb +56 -7
- data/lib/puma/minissl/context_builder.rb +14 -6
- data/lib/puma/minissl.rb +72 -40
- data/lib/puma/null_io.rb +12 -0
- data/lib/puma/plugin/tmp_restart.rb +0 -0
- data/lib/puma/plugin.rb +2 -2
- data/lib/puma/queue_close.rb +7 -7
- data/lib/puma/rack/builder.rb +1 -1
- data/lib/puma/rack/urlmap.rb +0 -0
- data/lib/puma/rack_default.rb +0 -0
- data/lib/puma/reactor.rb +19 -12
- data/lib/puma/request.rb +55 -21
- data/lib/puma/runner.rb +39 -13
- data/lib/puma/server.rb +78 -142
- data/lib/puma/single.rb +0 -0
- data/lib/puma/state_file.rb +45 -9
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +11 -8
- data/lib/puma/util.rb +8 -1
- data/lib/puma.rb +36 -10
- data/lib/rack/handler/puma.rb +1 -0
- data/tools/Dockerfile +1 -1
- data/tools/trickletest.rb +0 -0
- metadata +15 -9
data/lib/puma/configuration.rb
CHANGED
@@ -11,6 +11,7 @@ module Puma
|
|
11
11
|
|
12
12
|
DefaultTCPHost = "0.0.0.0"
|
13
13
|
DefaultTCPPort = 9292
|
14
|
+
DefaultWorkerCheckInterval = 5
|
14
15
|
DefaultWorkerTimeout = 60
|
15
16
|
DefaultWorkerShutdownTimeout = 30
|
16
17
|
end
|
@@ -92,6 +93,12 @@ module Puma
|
|
92
93
|
end
|
93
94
|
end
|
94
95
|
end
|
96
|
+
|
97
|
+
def final_options
|
98
|
+
default_options
|
99
|
+
.merge(file_options)
|
100
|
+
.merge(user_options)
|
101
|
+
end
|
95
102
|
end
|
96
103
|
|
97
104
|
# The main configuration class of Puma.
|
@@ -187,18 +194,24 @@ module Puma
|
|
187
194
|
:debug => false,
|
188
195
|
:binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
|
189
196
|
:workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
|
197
|
+
:silence_single_worker_warning => false,
|
190
198
|
:mode => :http,
|
199
|
+
:worker_check_interval => DefaultWorkerCheckInterval,
|
191
200
|
:worker_timeout => DefaultWorkerTimeout,
|
192
201
|
:worker_boot_timeout => DefaultWorkerTimeout,
|
193
202
|
:worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
|
203
|
+
:worker_culling_strategy => :youngest,
|
194
204
|
:remote_address => :socket,
|
195
205
|
:tag => method(:infer_tag),
|
196
|
-
:environment => -> { ENV['RACK_ENV'] || ENV['RAILS_ENV'] ||
|
206
|
+
:environment => -> { ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development' },
|
197
207
|
:rackup => DefaultRackup,
|
198
208
|
:logger => STDOUT,
|
199
209
|
:persistent_timeout => Const::PERSISTENT_TIMEOUT,
|
200
210
|
:first_data_timeout => Const::FIRST_DATA_TIMEOUT,
|
201
|
-
:raise_exception_on_sigterm => true
|
211
|
+
:raise_exception_on_sigterm => true,
|
212
|
+
:max_fast_inline => Const::MAX_FAST_INLINE,
|
213
|
+
:io_selector_backend => :auto,
|
214
|
+
:mutate_stdout_and_stderr_to_sync_on_write => true,
|
202
215
|
}
|
203
216
|
end
|
204
217
|
|
@@ -289,6 +302,10 @@ module Puma
|
|
289
302
|
end
|
290
303
|
end
|
291
304
|
|
305
|
+
def final_options
|
306
|
+
@options.final_options
|
307
|
+
end
|
308
|
+
|
292
309
|
def self.temp_path
|
293
310
|
require 'tmpdir'
|
294
311
|
|
@@ -329,6 +346,8 @@ module Puma
|
|
329
346
|
raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
|
330
347
|
|
331
348
|
rack_app, rack_options = rack_builder.parse_file(rackup)
|
349
|
+
rack_options = rack_options || {}
|
350
|
+
|
332
351
|
@options.file_options.merge!(rack_options)
|
333
352
|
|
334
353
|
config_ru_binds = []
|
data/lib/puma/const.rb
CHANGED
@@ -76,7 +76,7 @@ module Puma
|
|
76
76
|
508 => 'Loop Detected',
|
77
77
|
510 => 'Not Extended',
|
78
78
|
511 => 'Network Authentication Required'
|
79
|
-
}
|
79
|
+
}.freeze
|
80
80
|
|
81
81
|
# For some HTTP status codes the client only expects headers.
|
82
82
|
#
|
@@ -85,7 +85,7 @@ module Puma
|
|
85
85
|
204 => true,
|
86
86
|
205 => true,
|
87
87
|
304 => true
|
88
|
-
}
|
88
|
+
}.freeze
|
89
89
|
|
90
90
|
# Frequently used constants when constructing requests or responses. Many times
|
91
91
|
# the constant just refers to a string with the same contents. Using these constants
|
@@ -100,8 +100,8 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "5.
|
104
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "5.6.4".freeze
|
104
|
+
CODE_NAME = "Birdie's Version".freeze
|
105
105
|
|
106
106
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
107
107
|
|
@@ -145,9 +145,11 @@ module Puma
|
|
145
145
|
408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
|
146
146
|
# Indicate that there was an internal error, obviously.
|
147
147
|
500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
|
148
|
+
# Incorrect or invalid header value
|
149
|
+
501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze,
|
148
150
|
# A common header for indicating the server is too busy. Not used yet.
|
149
151
|
503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
|
150
|
-
}
|
152
|
+
}.freeze
|
151
153
|
|
152
154
|
# The basic max request size we'll try to read.
|
153
155
|
CHUNK_SIZE = 16 * 1024
|
@@ -228,7 +230,6 @@ module Puma
|
|
228
230
|
COLON = ": ".freeze
|
229
231
|
|
230
232
|
NEWLINE = "\n".freeze
|
231
|
-
HTTP_INJECTION_REGEX = /[\r\n]/.freeze
|
232
233
|
|
233
234
|
HIJACK_P = "rack.hijack?".freeze
|
234
235
|
HIJACK = "rack.hijack".freeze
|
@@ -236,8 +237,16 @@ module Puma
|
|
236
237
|
|
237
238
|
EARLY_HINTS = "rack.early_hints".freeze
|
238
239
|
|
239
|
-
#
|
240
|
-
|
240
|
+
# Illegal character in the key or value of response header
|
241
|
+
DQUOTE = "\"".freeze
|
242
|
+
HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
|
243
|
+
ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
|
244
|
+
# header values can contain HTAB?
|
245
|
+
ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
|
241
246
|
|
247
|
+
# Banned keys of response header
|
248
|
+
BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
|
249
|
+
|
250
|
+
PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
|
242
251
|
end
|
243
252
|
end
|
data/lib/puma/control_cli.rb
CHANGED
@@ -11,10 +11,32 @@ require 'socket'
|
|
11
11
|
module Puma
|
12
12
|
class ControlCLI
|
13
13
|
|
14
|
-
|
14
|
+
# values must be string or nil
|
15
|
+
# value of `nil` means command cannot be processed via signal
|
16
|
+
# @version 5.0.3
|
17
|
+
CMD_PATH_SIG_MAP = {
|
18
|
+
'gc' => nil,
|
19
|
+
'gc-stats' => nil,
|
20
|
+
'halt' => 'SIGQUIT',
|
21
|
+
'phased-restart' => 'SIGUSR1',
|
22
|
+
'refork' => 'SIGURG',
|
23
|
+
'reload-worker-directory' => nil,
|
24
|
+
'restart' => 'SIGUSR2',
|
25
|
+
'start' => nil,
|
26
|
+
'stats' => nil,
|
27
|
+
'status' => '',
|
28
|
+
'stop' => 'SIGTERM',
|
29
|
+
'thread-backtraces' => nil
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
# @deprecated 6.0.0
|
33
|
+
COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
|
34
|
+
|
35
|
+
# commands that cannot be used in a request
|
36
|
+
NO_REQ_COMMANDS = %w{refork}.freeze
|
15
37
|
|
16
38
|
# @version 5.0.0
|
17
|
-
PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}
|
39
|
+
PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}.freeze
|
18
40
|
|
19
41
|
def initialize(argv, stdout=STDOUT, stderr=STDERR)
|
20
42
|
@state = nil
|
@@ -25,7 +47,7 @@ module Puma
|
|
25
47
|
@control_auth_token = nil
|
26
48
|
@config_file = nil
|
27
49
|
@command = nil
|
28
|
-
@environment = ENV['RACK_ENV'] || ENV['RAILS_ENV']
|
50
|
+
@environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
|
29
51
|
|
30
52
|
@argv = argv.dup
|
31
53
|
@stdout = stdout
|
@@ -33,7 +55,7 @@ module Puma
|
|
33
55
|
@cli_options = {}
|
34
56
|
|
35
57
|
opts = OptionParser.new do |o|
|
36
|
-
o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{
|
58
|
+
o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
|
37
59
|
|
38
60
|
o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
|
39
61
|
@state = arg
|
@@ -74,7 +96,7 @@ module Puma
|
|
74
96
|
end
|
75
97
|
|
76
98
|
o.on_tail("-V", "--version", "Show version") do
|
77
|
-
puts Const::PUMA_VERSION
|
99
|
+
@stdout.puts Const::PUMA_VERSION
|
78
100
|
exit
|
79
101
|
end
|
80
102
|
end
|
@@ -86,10 +108,10 @@ module Puma
|
|
86
108
|
|
87
109
|
# check presence of command
|
88
110
|
unless @command
|
89
|
-
raise "Available commands: #{
|
111
|
+
raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
|
90
112
|
end
|
91
113
|
|
92
|
-
unless
|
114
|
+
unless CMD_PATH_SIG_MAP.key? @command
|
93
115
|
raise "Invalid command: #{@command}"
|
94
116
|
end
|
95
117
|
|
@@ -134,7 +156,7 @@ module Puma
|
|
134
156
|
@pid = sf.pid
|
135
157
|
elsif @pidfile
|
136
158
|
# get pid from pid_file
|
137
|
-
File.
|
159
|
+
@pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
|
138
160
|
end
|
139
161
|
end
|
140
162
|
|
@@ -142,24 +164,29 @@ module Puma
|
|
142
164
|
uri = URI.parse @control_url
|
143
165
|
|
144
166
|
# create server object by scheme
|
145
|
-
server =
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
167
|
+
server =
|
168
|
+
case uri.scheme
|
169
|
+
when 'ssl'
|
170
|
+
require 'openssl'
|
171
|
+
OpenSSL::SSL::SSLSocket.new(
|
172
|
+
TCPSocket.new(uri.host, uri.port),
|
173
|
+
OpenSSL::SSL::SSLContext.new)
|
174
|
+
.tap { |ssl| ssl.sync_close = true } # default is false
|
175
|
+
.tap(&:connect)
|
176
|
+
when 'tcp'
|
177
|
+
TCPSocket.new uri.host, uri.port
|
178
|
+
when 'unix'
|
179
|
+
# check for abstract UNIXSocket
|
180
|
+
UNIXSocket.new(@control_url.start_with?('unix://@') ?
|
181
|
+
"\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
|
182
|
+
else
|
183
|
+
raise "Invalid scheme: #{uri.scheme}"
|
184
|
+
end
|
185
|
+
|
186
|
+
if @command == 'status'
|
187
|
+
message 'Puma is started'
|
188
|
+
elsif NO_REQ_COMMANDS.include? @command
|
189
|
+
raise "Invalid request command: #{@command}"
|
163
190
|
else
|
164
191
|
url = "/#{@command}"
|
165
192
|
|
@@ -167,10 +194,10 @@ module Puma
|
|
167
194
|
url = url + "?token=#{@control_auth_token}"
|
168
195
|
end
|
169
196
|
|
170
|
-
server
|
197
|
+
server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
|
171
198
|
|
172
199
|
unless data = server.read
|
173
|
-
raise
|
200
|
+
raise 'Server closed connection before responding'
|
174
201
|
end
|
175
202
|
|
176
203
|
response = data.split("\r\n")
|
@@ -179,13 +206,13 @@ module Puma
|
|
179
206
|
raise "Server sent empty response"
|
180
207
|
end
|
181
208
|
|
182
|
-
|
209
|
+
@http, @code, @message = response.first.split(' ',3)
|
183
210
|
|
184
|
-
if @code ==
|
185
|
-
raise
|
186
|
-
elsif @code ==
|
211
|
+
if @code == '403'
|
212
|
+
raise 'Unauthorized access to server (wrong auth token)'
|
213
|
+
elsif @code == '404'
|
187
214
|
raise "Command error: #{response.last}"
|
188
|
-
elsif @code !=
|
215
|
+
elsif @code != '200'
|
189
216
|
raise "Bad response from server: #{@code}"
|
190
217
|
end
|
191
218
|
|
@@ -194,7 +221,7 @@ module Puma
|
|
194
221
|
end
|
195
222
|
ensure
|
196
223
|
if server
|
197
|
-
if uri.scheme ==
|
224
|
+
if uri.scheme == 'ssl'
|
198
225
|
server.sysclose
|
199
226
|
else
|
200
227
|
server.close unless server.closed?
|
@@ -204,51 +231,30 @@ module Puma
|
|
204
231
|
|
205
232
|
def send_signal
|
206
233
|
unless @pid
|
207
|
-
raise
|
234
|
+
raise 'Neither pid nor control url available'
|
208
235
|
end
|
209
236
|
|
210
237
|
begin
|
238
|
+
sig = CMD_PATH_SIG_MAP[@command]
|
211
239
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
when "halt"
|
217
|
-
Process.kill "QUIT", @pid
|
218
|
-
|
219
|
-
when "stop"
|
220
|
-
Process.kill "SIGTERM", @pid
|
221
|
-
|
222
|
-
when "stats"
|
223
|
-
puts "Stats not available via pid only"
|
224
|
-
return
|
225
|
-
|
226
|
-
when "reload-worker-directory"
|
227
|
-
puts "reload-worker-directory not available via pid only"
|
240
|
+
if sig.nil?
|
241
|
+
@stdout.puts "'#{@command}' not available via pid only"
|
242
|
+
@stdout.flush unless @stdout.sync
|
228
243
|
return
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
when "status"
|
244
|
+
elsif sig.start_with? 'SIG'
|
245
|
+
Process.kill sig, @pid
|
246
|
+
elsif @command == 'status'
|
234
247
|
begin
|
235
248
|
Process.kill 0, @pid
|
236
|
-
puts
|
249
|
+
@stdout.puts 'Puma is started'
|
250
|
+
@stdout.flush unless @stdout.sync
|
237
251
|
rescue Errno::ESRCH
|
238
|
-
raise
|
252
|
+
raise 'Puma is not running'
|
239
253
|
end
|
240
|
-
|
241
|
-
return
|
242
|
-
|
243
|
-
when "refork"
|
244
|
-
Process.kill "SIGURG", @pid
|
245
|
-
|
246
|
-
else
|
247
254
|
return
|
248
255
|
end
|
249
|
-
|
250
256
|
rescue SystemCallError
|
251
|
-
if @command ==
|
257
|
+
if @command == 'restart'
|
252
258
|
start
|
253
259
|
else
|
254
260
|
raise "No pid '#{@pid}' found"
|
@@ -259,14 +265,13 @@ module Puma
|
|
259
265
|
end
|
260
266
|
|
261
267
|
def run
|
262
|
-
return start if @command ==
|
263
|
-
|
268
|
+
return start if @command == 'start'
|
264
269
|
prepare_configuration
|
265
270
|
|
266
|
-
if Puma.windows?
|
271
|
+
if Puma.windows? || @control_url
|
267
272
|
send_request
|
268
273
|
else
|
269
|
-
|
274
|
+
send_signal
|
270
275
|
end
|
271
276
|
|
272
277
|
rescue => e
|
data/lib/puma/detect.rb
CHANGED
@@ -1,20 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# This file can be loaded independently of puma.rb, so it cannot have any code
|
4
|
+
# that assumes puma.rb is loaded.
|
5
|
+
|
6
|
+
|
3
7
|
module Puma
|
4
|
-
#
|
5
|
-
|
8
|
+
# @version 5.2.1
|
9
|
+
HAS_FORK = ::Process.respond_to? :fork
|
6
10
|
|
7
|
-
|
8
|
-
HAS_SSL
|
9
|
-
end
|
11
|
+
IS_JRUBY = Object.const_defined? :JRUBY_VERSION
|
10
12
|
|
11
|
-
|
13
|
+
IS_OSX = RUBY_PLATFORM.include? 'darwin'
|
14
|
+
|
15
|
+
IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
|
16
|
+
IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
|
17
|
+
|
18
|
+
# @version 5.2.0
|
19
|
+
IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
|
12
20
|
|
13
21
|
def self.jruby?
|
14
22
|
IS_JRUBY
|
15
23
|
end
|
16
24
|
|
17
|
-
|
25
|
+
def self.osx?
|
26
|
+
IS_OSX
|
27
|
+
end
|
18
28
|
|
19
29
|
def self.windows?
|
20
30
|
IS_WINDOWS
|
@@ -22,11 +32,11 @@ module Puma
|
|
22
32
|
|
23
33
|
# @version 5.0.0
|
24
34
|
def self.mri?
|
25
|
-
|
35
|
+
IS_MRI
|
26
36
|
end
|
27
37
|
|
28
38
|
# @version 5.0.0
|
29
39
|
def self.forkable?
|
30
|
-
|
40
|
+
HAS_FORK
|
31
41
|
end
|
32
42
|
end
|