puma 3.12.6 → 4.3.10
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 +145 -3
- data/README.md +76 -48
- data/docs/architecture.md +1 -0
- data/docs/deployment.md +24 -4
- 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/plugins.md +20 -10
- data/docs/restart.md +4 -2
- data/docs/systemd.md +27 -9
- data/docs/tcp_mode.md +96 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/extconf.rb +13 -0
- data/ext/puma_http11/http11_parser.c +58 -70
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser_common.rl +4 -4
- data/ext/puma_http11/mini_ssl.c +78 -8
- data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +86 -99
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -4
- data/ext/puma_http11/puma_http11.c +3 -0
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +37 -29
- data/lib/puma/binder.rb +38 -60
- data/lib/puma/cli.rb +4 -0
- data/lib/puma/client.rb +242 -208
- data/lib/puma/cluster.rb +53 -30
- data/lib/puma/configuration.rb +4 -3
- data/lib/puma/const.rb +22 -18
- data/lib/puma/control_cli.rb +30 -5
- data/lib/puma/dsl.rb +299 -75
- data/lib/puma/events.rb +4 -1
- data/lib/puma/io_buffer.rb +1 -6
- data/lib/puma/launcher.rb +95 -53
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/minissl.rb +35 -17
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/plugin.rb +5 -2
- data/lib/puma/rack/builder.rb +2 -0
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +110 -57
- data/lib/puma/runner.rb +11 -3
- data/lib/puma/server.rb +73 -57
- data/lib/puma/single.rb +3 -3
- data/lib/puma/thread_pool.rb +15 -33
- data/lib/puma/util.rb +1 -6
- data/lib/puma.rb +8 -0
- data/lib/rack/handler/puma.rb +3 -3
- data/tools/docker/Dockerfile +16 -0
- data/tools/jungle/init.d/puma +6 -6
- data/tools/trickletest.rb +0 -1
- metadata +26 -13
- 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/cluster.rb
CHANGED
@@ -19,8 +19,6 @@ module Puma
|
|
19
19
|
# via the `spawn_workers` method call. Each worker will have it's own
|
20
20
|
# instance of a `Puma::Server`.
|
21
21
|
class Cluster < Runner
|
22
|
-
WORKER_CHECK_INTERVAL = 5
|
23
|
-
|
24
22
|
def initialize(cli, events)
|
25
23
|
super cli, events
|
26
24
|
|
@@ -37,7 +35,11 @@ module Puma
|
|
37
35
|
@workers.each { |x| x.term }
|
38
36
|
|
39
37
|
begin
|
40
|
-
|
38
|
+
loop do
|
39
|
+
wait_workers
|
40
|
+
break if @workers.empty?
|
41
|
+
sleep 0.2
|
42
|
+
end
|
41
43
|
rescue Interrupt
|
42
44
|
log "! Cancelled waiting for workers"
|
43
45
|
end
|
@@ -69,12 +71,13 @@ module Puma
|
|
69
71
|
@signal = "TERM"
|
70
72
|
@options = options
|
71
73
|
@first_term_sent = nil
|
74
|
+
@started_at = Time.now
|
72
75
|
@last_checkin = Time.now
|
73
76
|
@last_status = '{}'
|
74
|
-
@
|
77
|
+
@term = false
|
75
78
|
end
|
76
79
|
|
77
|
-
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status
|
80
|
+
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
78
81
|
|
79
82
|
def booted?
|
80
83
|
@stage == :booted
|
@@ -85,12 +88,8 @@ module Puma
|
|
85
88
|
@stage = :booted
|
86
89
|
end
|
87
90
|
|
88
|
-
def
|
89
|
-
@
|
90
|
-
end
|
91
|
-
|
92
|
-
def dead!
|
93
|
-
@dead = true
|
91
|
+
def term?
|
92
|
+
@term
|
94
93
|
end
|
95
94
|
|
96
95
|
def ping!(status)
|
@@ -107,9 +106,9 @@ module Puma
|
|
107
106
|
if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
|
108
107
|
@signal = "KILL"
|
109
108
|
else
|
109
|
+
@term ||= true
|
110
110
|
@first_term_sent ||= Time.now
|
111
111
|
end
|
112
|
-
|
113
112
|
Process.kill @signal, @pid
|
114
113
|
rescue Errno::ESRCH
|
115
114
|
end
|
@@ -183,7 +182,7 @@ module Puma
|
|
183
182
|
def check_workers(force=false)
|
184
183
|
return if !force && @next_check && @next_check >= Time.now
|
185
184
|
|
186
|
-
@next_check = Time.now + WORKER_CHECK_INTERVAL
|
185
|
+
@next_check = Time.now + Const::WORKER_CHECK_INTERVAL
|
187
186
|
|
188
187
|
any = false
|
189
188
|
|
@@ -200,15 +199,7 @@ module Puma
|
|
200
199
|
# during this loop by giving the kernel time to kill them.
|
201
200
|
sleep 1 if any
|
202
201
|
|
203
|
-
|
204
|
-
pid = Process.waitpid(-1, Process::WNOHANG)
|
205
|
-
break unless pid
|
206
|
-
|
207
|
-
@workers.delete_if { |w| w.pid == pid }
|
208
|
-
end
|
209
|
-
|
210
|
-
@workers.delete_if(&:dead?)
|
211
|
-
|
202
|
+
wait_workers
|
212
203
|
cull_workers
|
213
204
|
spawn_workers
|
214
205
|
|
@@ -225,8 +216,10 @@ module Puma
|
|
225
216
|
log "- Stopping #{w.pid} for phased upgrade..."
|
226
217
|
end
|
227
218
|
|
228
|
-
w.term
|
229
|
-
|
219
|
+
unless w.term?
|
220
|
+
w.term
|
221
|
+
log "- #{w.signal} sent to #{w.pid}..."
|
222
|
+
end
|
230
223
|
end
|
231
224
|
end
|
232
225
|
end
|
@@ -253,6 +246,7 @@ module Puma
|
|
253
246
|
@suicide_pipe.close
|
254
247
|
|
255
248
|
Thread.new do
|
249
|
+
Puma.set_thread_name "worker check pipe"
|
256
250
|
IO.select [@check_pipe]
|
257
251
|
log "! Detected parent died, dying"
|
258
252
|
exit! 1
|
@@ -275,6 +269,7 @@ module Puma
|
|
275
269
|
server = start_server
|
276
270
|
|
277
271
|
Signal.trap "SIGTERM" do
|
272
|
+
@worker_write << "e#{Process.pid}\n" rescue nil
|
278
273
|
server.stop
|
279
274
|
end
|
280
275
|
|
@@ -287,10 +282,11 @@ module Puma
|
|
287
282
|
end
|
288
283
|
|
289
284
|
Thread.new(@worker_write) do |io|
|
285
|
+
Puma.set_thread_name "stat payload"
|
290
286
|
base_payload = "p#{Process.pid}"
|
291
287
|
|
292
288
|
while true
|
293
|
-
sleep WORKER_CHECK_INTERVAL
|
289
|
+
sleep Const::WORKER_CHECK_INTERVAL
|
294
290
|
begin
|
295
291
|
b = server.backlog || 0
|
296
292
|
r = server.running || 0
|
@@ -352,11 +348,13 @@ module Puma
|
|
352
348
|
Dir.chdir dir
|
353
349
|
end
|
354
350
|
|
351
|
+
# Inside of a child process, this will return all zeroes, as @workers is only populated in
|
352
|
+
# the master process.
|
355
353
|
def stats
|
356
354
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
357
355
|
booted_worker_count = @workers.count { |w| w.booted? }
|
358
|
-
worker_status = '[' + @workers.map { |w| %Q!{ "pid": #{w.pid}, "index": #{w.index}, "phase": #{w.phase}, "booted": #{w.booted?}, "last_checkin": "#{w.last_checkin.utc.iso8601}", "last_status": #{w.last_status} }!}.join(",") + ']'
|
359
|
-
%Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }!
|
356
|
+
worker_status = '[' + @workers.map { |w| %Q!{ "started_at": "#{w.started_at.utc.iso8601}", "pid": #{w.pid}, "index": #{w.index}, "phase": #{w.phase}, "booted": #{w.booted?}, "last_checkin": "#{w.last_checkin.utc.iso8601}", "last_status": #{w.last_status} }!}.join(",") + ']'
|
357
|
+
%Q!{ "started_at": "#{@started_at.utc.iso8601}", "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }!
|
360
358
|
end
|
361
359
|
|
362
360
|
def preload?
|
@@ -390,10 +388,13 @@ module Puma
|
|
390
388
|
log "Early termination of worker"
|
391
389
|
exit! 0
|
392
390
|
else
|
391
|
+
@launcher.close_binder_listeners
|
392
|
+
|
393
393
|
stop_workers
|
394
394
|
stop
|
395
395
|
|
396
|
-
raise
|
396
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
397
|
+
exit 0 # Clean exit, workers were stopped
|
397
398
|
end
|
398
399
|
end
|
399
400
|
end
|
@@ -487,7 +488,7 @@ module Puma
|
|
487
488
|
|
488
489
|
force_check = false
|
489
490
|
|
490
|
-
res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
|
491
|
+
res = IO.select([read], nil, nil, Const::WORKER_CHECK_INTERVAL)
|
491
492
|
|
492
493
|
if res
|
493
494
|
req = read.read_nonblock(1)
|
@@ -503,8 +504,11 @@ module Puma
|
|
503
504
|
w.boot!
|
504
505
|
log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
|
505
506
|
force_check = true
|
507
|
+
when "e"
|
508
|
+
# external term, see worker method, Signal.trap "SIGTERM"
|
509
|
+
w.instance_variable_set :@term, true
|
506
510
|
when "t"
|
507
|
-
w.
|
511
|
+
w.term unless w.term?
|
508
512
|
force_check = true
|
509
513
|
when "p"
|
510
514
|
w.ping!(result.sub(/^\d+/,'').chomp)
|
@@ -527,5 +531,24 @@ module Puma
|
|
527
531
|
@wakeup.close
|
528
532
|
end
|
529
533
|
end
|
534
|
+
|
535
|
+
private
|
536
|
+
|
537
|
+
# loops thru @workers, removing workers that exited, and calling
|
538
|
+
# `#term` if needed
|
539
|
+
def wait_workers
|
540
|
+
@workers.reject! do |w|
|
541
|
+
begin
|
542
|
+
if Process.wait(w.pid, Process::WNOHANG)
|
543
|
+
true
|
544
|
+
else
|
545
|
+
w.term if w.term?
|
546
|
+
nil
|
547
|
+
end
|
548
|
+
rescue Errno::ECHILD
|
549
|
+
true # child is already terminated
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|
530
553
|
end
|
531
554
|
end
|
data/lib/puma/configuration.rb
CHANGED
@@ -20,7 +20,7 @@ module Puma
|
|
20
20
|
# In this class any "user" specified options take precedence over any
|
21
21
|
# "file" specified options, take precedence over any "default" options.
|
22
22
|
#
|
23
|
-
# User input is
|
23
|
+
# User input is preferred over "defaults":
|
24
24
|
# user_options = { foo: "bar" }
|
25
25
|
# default_options = { foo: "zoo" }
|
26
26
|
# options = UserFileDefaultOptions.new(user_options, default_options)
|
@@ -32,7 +32,7 @@ module Puma
|
|
32
32
|
# puts options.all_of(:foo)
|
33
33
|
# # => ["bar", "zoo"]
|
34
34
|
#
|
35
|
-
# A "file" option can be set. This config will be
|
35
|
+
# A "file" option can be set. This config will be preferred over "default" options
|
36
36
|
# but will defer to any available "user" specified options.
|
37
37
|
#
|
38
38
|
# user_options = { foo: "bar" }
|
@@ -186,7 +186,8 @@ module Puma
|
|
186
186
|
:rackup => DefaultRackup,
|
187
187
|
:logger => STDOUT,
|
188
188
|
:persistent_timeout => Const::PERSISTENT_TIMEOUT,
|
189
|
-
:first_data_timeout => Const::FIRST_DATA_TIMEOUT
|
189
|
+
:first_data_timeout => Const::FIRST_DATA_TIMEOUT,
|
190
|
+
:raise_exception_on_sigterm => true
|
190
191
|
}
|
191
192
|
end
|
192
193
|
|
data/lib/puma/const.rb
CHANGED
@@ -100,8 +100,8 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "3.
|
104
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "4.3.10".freeze
|
104
|
+
CODE_NAME = "Mysterious Traveller".freeze
|
105
105
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
106
106
|
|
107
107
|
FAST_TRACK_KA_TIMEOUT = 0.2
|
@@ -129,27 +129,24 @@ module Puma
|
|
129
129
|
REQUEST_URI= 'REQUEST_URI'.freeze
|
130
130
|
REQUEST_PATH = 'REQUEST_PATH'.freeze
|
131
131
|
QUERY_STRING = 'QUERY_STRING'.freeze
|
132
|
+
CONTENT_LENGTH = "CONTENT_LENGTH".freeze
|
132
133
|
|
133
134
|
PATH_INFO = 'PATH_INFO'.freeze
|
134
135
|
|
135
136
|
PUMA_TMP_BASE = "puma".freeze
|
136
137
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
|
150
|
-
|
151
|
-
# A common header for indicating the server is too busy. Not used yet.
|
152
|
-
ERROR_503_RESPONSE = "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
|
138
|
+
ERROR_RESPONSE = {
|
139
|
+
# Indicate that we couldn't parse the request
|
140
|
+
400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
|
141
|
+
# The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
|
142
|
+
404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,
|
143
|
+
# The standard empty 408 response for requests that timed out.
|
144
|
+
408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
|
145
|
+
# Indicate that there was an internal error, obviously.
|
146
|
+
500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
|
147
|
+
# A common header for indicating the server is too busy. Not used yet.
|
148
|
+
503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
|
149
|
+
}
|
153
150
|
|
154
151
|
# The basic max request size we'll try to read.
|
155
152
|
CHUNK_SIZE = 16 * 1024
|
@@ -167,6 +164,9 @@ module Puma
|
|
167
164
|
LINE_END = "\r\n".freeze
|
168
165
|
REMOTE_ADDR = "REMOTE_ADDR".freeze
|
169
166
|
HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
|
167
|
+
HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL".freeze
|
168
|
+
HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME".freeze
|
169
|
+
HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO".freeze
|
170
170
|
|
171
171
|
SERVER_NAME = "SERVER_NAME".freeze
|
172
172
|
SERVER_PORT = "SERVER_PORT".freeze
|
@@ -235,5 +235,9 @@ module Puma
|
|
235
235
|
HIJACK_IO = "rack.hijack_io".freeze
|
236
236
|
|
237
237
|
EARLY_HINTS = "rack.early_hints".freeze
|
238
|
+
|
239
|
+
# Mininum interval to checks worker health
|
240
|
+
WORKER_CHECK_INTERVAL = 5
|
241
|
+
|
238
242
|
end
|
239
243
|
end
|
data/lib/puma/control_cli.rb
CHANGED
@@ -22,6 +22,7 @@ module Puma
|
|
22
22
|
@control_auth_token = nil
|
23
23
|
@config_file = nil
|
24
24
|
@command = nil
|
25
|
+
@environment = ENV['RACK_ENV']
|
25
26
|
|
26
27
|
@argv = argv.dup
|
27
28
|
@stdout = stdout
|
@@ -59,6 +60,11 @@ module Puma
|
|
59
60
|
@config_file = arg
|
60
61
|
end
|
61
62
|
|
63
|
+
o.on "-e", "--environment ENVIRONMENT",
|
64
|
+
"The environment to run the Rack app on (default development)" do |arg|
|
65
|
+
@environment = arg
|
66
|
+
end
|
67
|
+
|
62
68
|
o.on_tail("-H", "--help", "Show this message") do
|
63
69
|
@stdout.puts o
|
64
70
|
exit
|
@@ -76,8 +82,12 @@ module Puma
|
|
76
82
|
@command = argv.shift
|
77
83
|
|
78
84
|
unless @config_file == '-'
|
79
|
-
|
80
|
-
|
85
|
+
environment = @environment || 'development'
|
86
|
+
|
87
|
+
if @config_file.nil?
|
88
|
+
@config_file = %W(config/puma/#{environment}.rb config/puma.rb).find do |f|
|
89
|
+
File.exist?(f)
|
90
|
+
end
|
81
91
|
end
|
82
92
|
|
83
93
|
if @config_file
|
@@ -101,7 +111,6 @@ module Puma
|
|
101
111
|
|
102
112
|
rescue => e
|
103
113
|
@stdout.puts e.message
|
104
|
-
@stdout.puts e.backtrace
|
105
114
|
exit 1
|
106
115
|
end
|
107
116
|
|
@@ -123,7 +132,7 @@ module Puma
|
|
123
132
|
@pid = sf.pid
|
124
133
|
elsif @pidfile
|
125
134
|
# get pid from pid_file
|
126
|
-
|
135
|
+
File.open(@pidfile) { |f| @pid = f.read.to_i }
|
127
136
|
end
|
128
137
|
end
|
129
138
|
|
@@ -132,6 +141,12 @@ module Puma
|
|
132
141
|
|
133
142
|
# create server object by scheme
|
134
143
|
server = case uri.scheme
|
144
|
+
when "ssl"
|
145
|
+
require 'openssl'
|
146
|
+
OpenSSL::SSL::SSLSocket.new(
|
147
|
+
TCPSocket.new(uri.host, uri.port),
|
148
|
+
OpenSSL::SSL::SSLContext.new
|
149
|
+
).tap(&:connect)
|
135
150
|
when "tcp"
|
136
151
|
TCPSocket.new uri.host, uri.port
|
137
152
|
when "unix"
|
@@ -206,6 +221,16 @@ module Puma
|
|
206
221
|
when "phased-restart"
|
207
222
|
Process.kill "SIGUSR1", @pid
|
208
223
|
|
224
|
+
when "status"
|
225
|
+
begin
|
226
|
+
Process.kill 0, @pid
|
227
|
+
puts "Puma is started"
|
228
|
+
rescue Errno::ESRCH
|
229
|
+
raise "Puma is not running"
|
230
|
+
end
|
231
|
+
|
232
|
+
return
|
233
|
+
|
209
234
|
else
|
210
235
|
return
|
211
236
|
end
|
@@ -234,7 +259,6 @@ module Puma
|
|
234
259
|
|
235
260
|
rescue => e
|
236
261
|
message e.message
|
237
|
-
message e.backtrace
|
238
262
|
exit 1
|
239
263
|
end
|
240
264
|
|
@@ -250,6 +274,7 @@ module Puma
|
|
250
274
|
run_args += ["--control-url", @control_url] if @control_url
|
251
275
|
run_args += ["--control-token", @control_auth_token] if @control_auth_token
|
252
276
|
run_args += ["-C", @config_file] if @config_file
|
277
|
+
run_args += ["-e", @environment] if @environment
|
253
278
|
|
254
279
|
events = Puma::Events.new @stdout, @stderr
|
255
280
|
|