puma 3.12.0 → 4.0.0
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 +36 -0
- data/README.md +29 -9
- data/docs/architecture.md +1 -0
- data/docs/deployment.md +24 -4
- data/docs/restart.md +4 -2
- data/docs/systemd.md +27 -9
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/mini_ssl.c +32 -4
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +11 -4
- data/lib/puma/app/status.rb +3 -2
- data/lib/puma/binder.rb +19 -10
- data/lib/puma/cli.rb +2 -0
- data/lib/puma/client.rb +46 -25
- data/lib/puma/cluster.rb +40 -14
- data/lib/puma/commonlogger.rb +2 -0
- data/lib/puma/configuration.rb +4 -1
- data/lib/puma/const.rb +8 -2
- data/lib/puma/control_cli.rb +21 -9
- data/lib/puma/convenient.rb +2 -0
- data/lib/puma/daemon_ext.rb +2 -0
- data/lib/puma/delegation.rb +2 -0
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +57 -5
- data/lib/puma/events.rb +2 -0
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -0
- data/lib/puma/launcher.rb +14 -13
- data/lib/puma/minissl.rb +15 -1
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin.rb +2 -0
- data/lib/puma/rack/builder.rb +2 -1
- data/lib/puma/reactor.rb +106 -53
- data/lib/puma/runner.rb +3 -1
- data/lib/puma/server.rb +27 -24
- data/lib/puma/single.rb +4 -2
- data/lib/puma/state_file.rb +2 -0
- data/lib/puma/tcp_logger.rb +2 -0
- data/lib/puma/thread_pool.rb +7 -1
- data/lib/puma/util.rb +2 -6
- data/lib/rack/handler/puma.rb +3 -0
- data/tools/jungle/init.d/puma +5 -5
- metadata +19 -8
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
@@ -23,6 +23,7 @@ import javax.net.ssl.SSLPeerUnverifiedException;
|
|
23
23
|
import javax.net.ssl.SSLSession;
|
24
24
|
import java.io.FileInputStream;
|
25
25
|
import java.io.IOException;
|
26
|
+
import java.nio.Buffer;
|
26
27
|
import java.nio.ByteBuffer;
|
27
28
|
import java.security.KeyManagementException;
|
28
29
|
import java.security.KeyStore;
|
@@ -65,7 +66,7 @@ public class MiniSSL extends RubyObject {
|
|
65
66
|
|
66
67
|
public void clear() { buffer.clear(); }
|
67
68
|
public void compact() { buffer.compact(); }
|
68
|
-
public void flip() { buffer.flip(); }
|
69
|
+
public void flip() { ((Buffer) buffer).flip(); }
|
69
70
|
public boolean hasRemaining() { return buffer.hasRemaining(); }
|
70
71
|
public int position() { return buffer.position(); }
|
71
72
|
|
@@ -89,7 +90,7 @@ public class MiniSSL extends RubyObject {
|
|
89
90
|
public void resize(int newCapacity) {
|
90
91
|
if (newCapacity > buffer.capacity()) {
|
91
92
|
ByteBuffer dstTmp = ByteBuffer.allocate(newCapacity);
|
92
|
-
|
93
|
+
flip();
|
93
94
|
dstTmp.put(buffer);
|
94
95
|
buffer = dstTmp;
|
95
96
|
} else {
|
@@ -101,7 +102,7 @@ public class MiniSSL extends RubyObject {
|
|
101
102
|
* Drains the buffer to a ByteList, or returns null for an empty buffer
|
102
103
|
*/
|
103
104
|
public ByteList asByteList() {
|
104
|
-
|
105
|
+
flip();
|
105
106
|
if (!buffer.hasRemaining()) {
|
106
107
|
buffer.clear();
|
107
108
|
return null;
|
@@ -158,7 +159,13 @@ public class MiniSSL extends RubyObject {
|
|
158
159
|
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
|
159
160
|
engine = sslCtx.createSSLEngine();
|
160
161
|
|
161
|
-
String[] protocols
|
162
|
+
String[] protocols;
|
163
|
+
if(miniSSLContext.callMethod(threadContext, "no_tlsv1").isTrue()) {
|
164
|
+
protocols = new String[] { "TLSv1.1", "TLSv1.2" };
|
165
|
+
} else {
|
166
|
+
protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
|
167
|
+
}
|
168
|
+
|
162
169
|
engine.setEnabledProtocols(protocols);
|
163
170
|
engine.setUseClientMode(false);
|
164
171
|
|
data/lib/puma/app/status.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
1
3
|
module Puma
|
2
4
|
module App
|
3
5
|
class Status
|
@@ -60,8 +62,7 @@ module Puma
|
|
60
62
|
return rack_response(200, OK_STATUS)
|
61
63
|
|
62
64
|
when /\/gc-stats$/
|
63
|
-
|
64
|
-
return rack_response(200, json)
|
65
|
+
return rack_response(200, GC.stat.to_json)
|
65
66
|
|
66
67
|
when /\/stats$/
|
67
68
|
return rack_response(200, @cli.stats)
|
data/lib/puma/binder.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
require 'socket'
|
3
5
|
|
@@ -48,7 +50,13 @@ module Puma
|
|
48
50
|
|
49
51
|
def close
|
50
52
|
@ios.each { |i| i.close }
|
51
|
-
@unix_paths.each
|
53
|
+
@unix_paths.each do |i|
|
54
|
+
# Errno::ENOENT is intermittently raised
|
55
|
+
begin
|
56
|
+
File.unlink i
|
57
|
+
rescue Errno::ENOENT
|
58
|
+
end
|
59
|
+
end
|
52
60
|
end
|
53
61
|
|
54
62
|
def import_from_env
|
@@ -90,19 +98,19 @@ module Puma
|
|
90
98
|
case uri.scheme
|
91
99
|
when "tcp"
|
92
100
|
if fd = @inherited_fds.delete(str)
|
93
|
-
logger.log "* Inherited #{str}"
|
94
101
|
io = inherit_tcp_listener uri.host, uri.port, fd
|
102
|
+
logger.log "* Inherited #{str}"
|
95
103
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
96
|
-
logger.log "* Activated #{str}"
|
97
104
|
io = inherit_tcp_listener uri.host, uri.port, sock
|
105
|
+
logger.log "* Activated #{str}"
|
98
106
|
else
|
99
107
|
params = Util.parse_query uri.query
|
100
108
|
|
101
109
|
opt = params.key?('low_latency')
|
102
110
|
bak = params.fetch('backlog', 1024).to_i
|
103
111
|
|
104
|
-
logger.log "* Listening on #{str}"
|
105
112
|
io = add_tcp_listener uri.host, uri.port, opt, bak
|
113
|
+
logger.log "* Listening on #{str}"
|
106
114
|
end
|
107
115
|
|
108
116
|
@listeners << [str, io] if io
|
@@ -110,14 +118,12 @@ module Puma
|
|
110
118
|
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
|
111
119
|
|
112
120
|
if fd = @inherited_fds.delete(str)
|
113
|
-
logger.log "* Inherited #{str}"
|
114
121
|
io = inherit_unix_listener path, fd
|
122
|
+
logger.log "* Inherited #{str}"
|
115
123
|
elsif sock = @activated_sockets.delete([ :unix, path ])
|
116
|
-
logger.log "* Activated #{str}"
|
117
124
|
io = inherit_unix_listener path, sock
|
125
|
+
logger.log "* Activated #{str}"
|
118
126
|
else
|
119
|
-
logger.log "* Listening on #{str}"
|
120
|
-
|
121
127
|
umask = nil
|
122
128
|
mode = nil
|
123
129
|
backlog = 1024
|
@@ -139,6 +145,7 @@ module Puma
|
|
139
145
|
end
|
140
146
|
|
141
147
|
io = add_unix_listener path, umask, mode, backlog
|
148
|
+
logger.log "* Listening on #{str}"
|
142
149
|
end
|
143
150
|
|
144
151
|
@listeners << [str, io]
|
@@ -186,6 +193,8 @@ module Puma
|
|
186
193
|
ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
|
187
194
|
end
|
188
195
|
|
196
|
+
ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
|
197
|
+
|
189
198
|
if params['verify_mode']
|
190
199
|
ctx.verify_mode = case params['verify_mode']
|
191
200
|
when "peer"
|
@@ -204,11 +213,11 @@ module Puma
|
|
204
213
|
logger.log "* Inherited #{str}"
|
205
214
|
io = inherit_ssl_listener fd, ctx
|
206
215
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
207
|
-
logger.log "* Activated #{str}"
|
208
216
|
io = inherit_ssl_listener sock, ctx
|
217
|
+
logger.log "* Activated #{str}"
|
209
218
|
else
|
210
|
-
logger.log "* Listening on #{str}"
|
211
219
|
io = add_ssl_listener uri.host, uri.port, ctx
|
220
|
+
logger.log "* Listening on #{str}"
|
212
221
|
end
|
213
222
|
|
214
223
|
@listeners << [str, io] if io
|
data/lib/puma/cli.rb
CHANGED
data/lib/puma/client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class IO
|
2
4
|
# We need to use this for a jruby work around on both 1.8 and 1.9.
|
3
5
|
# So this either creates the constant (on 1.8), or harmlessly
|
@@ -25,9 +27,10 @@ module Puma
|
|
25
27
|
# For example a web request from a browser or from CURL. This
|
26
28
|
#
|
27
29
|
# An instance of `Puma::Client` can be used as if it were an IO object
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
30
|
+
# by the reactor, that's because the latter is expected to call `#to_io`
|
31
|
+
# on any non-IO objects it polls. For example nio4r internally calls
|
32
|
+
# `IO::try_convert` (which may call `#to_io`) when a new socket is
|
33
|
+
# registered.
|
31
34
|
#
|
32
35
|
# Instances of this class are responsible for knowing if
|
33
36
|
# the header and body are fully buffered via the `try_to_finish` method.
|
@@ -52,6 +55,7 @@ module Puma
|
|
52
55
|
@ready = false
|
53
56
|
|
54
57
|
@body = nil
|
58
|
+
@body_read_start = nil
|
55
59
|
@buffer = nil
|
56
60
|
@tempfile = nil
|
57
61
|
|
@@ -62,6 +66,8 @@ module Puma
|
|
62
66
|
|
63
67
|
@peerip = nil
|
64
68
|
@remote_addr_header = nil
|
69
|
+
|
70
|
+
@body_remain = 0
|
65
71
|
end
|
66
72
|
|
67
73
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
@@ -100,6 +106,8 @@ module Puma
|
|
100
106
|
@tempfile = nil
|
101
107
|
@parsed_bytes = 0
|
102
108
|
@ready = false
|
109
|
+
@body_remain = 0
|
110
|
+
@peerip = nil
|
103
111
|
|
104
112
|
if @buffer
|
105
113
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
@@ -112,9 +120,16 @@ module Puma
|
|
112
120
|
end
|
113
121
|
|
114
122
|
return false
|
115
|
-
|
116
|
-
|
117
|
-
|
123
|
+
else
|
124
|
+
begin
|
125
|
+
if fast_check &&
|
126
|
+
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
127
|
+
return try_to_finish
|
128
|
+
end
|
129
|
+
rescue IOError
|
130
|
+
# swallow it
|
131
|
+
end
|
132
|
+
|
118
133
|
end
|
119
134
|
end
|
120
135
|
|
@@ -145,8 +160,11 @@ module Puma
|
|
145
160
|
def decode_chunk(chunk)
|
146
161
|
if @partial_part_left > 0
|
147
162
|
if @partial_part_left <= chunk.size
|
148
|
-
|
163
|
+
if @partial_part_left > 2
|
164
|
+
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
165
|
+
end
|
149
166
|
chunk = chunk[@partial_part_left..-1]
|
167
|
+
@partial_part_left = 0
|
150
168
|
else
|
151
169
|
@body << chunk
|
152
170
|
@partial_part_left -= chunk.size
|
@@ -168,9 +186,9 @@ module Puma
|
|
168
186
|
if len == 0
|
169
187
|
@body.rewind
|
170
188
|
rest = io.read
|
189
|
+
rest = rest[2..-1] if rest.start_with?("\r\n")
|
171
190
|
@buffer = rest.empty? ? nil : rest
|
172
|
-
|
173
|
-
@ready = true
|
191
|
+
set_ready
|
174
192
|
return true
|
175
193
|
end
|
176
194
|
|
@@ -208,7 +226,7 @@ module Puma
|
|
208
226
|
while true
|
209
227
|
begin
|
210
228
|
chunk = @io.read_nonblock(4096)
|
211
|
-
rescue
|
229
|
+
rescue IO::WaitReadable
|
212
230
|
return false
|
213
231
|
rescue SystemCallError, IOError
|
214
232
|
raise ConnectionError, "Connection error detected during read"
|
@@ -218,8 +236,7 @@ module Puma
|
|
218
236
|
unless chunk
|
219
237
|
@body.close
|
220
238
|
@buffer = nil
|
221
|
-
|
222
|
-
@ready = true
|
239
|
+
set_ready
|
223
240
|
raise EOFError
|
224
241
|
end
|
225
242
|
|
@@ -228,6 +245,8 @@ module Puma
|
|
228
245
|
end
|
229
246
|
|
230
247
|
def setup_body
|
248
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
249
|
+
|
231
250
|
if @env[HTTP_EXPECT] == CONTINUE
|
232
251
|
# TODO allow a hook here to check the headers before
|
233
252
|
# going forward
|
@@ -252,8 +271,7 @@ module Puma
|
|
252
271
|
unless cl
|
253
272
|
@buffer = body.empty? ? nil : body
|
254
273
|
@body = EmptyBody
|
255
|
-
|
256
|
-
@ready = true
|
274
|
+
set_ready
|
257
275
|
return true
|
258
276
|
end
|
259
277
|
|
@@ -262,8 +280,7 @@ module Puma
|
|
262
280
|
if remain <= 0
|
263
281
|
@body = StringIO.new(body)
|
264
282
|
@buffer = nil
|
265
|
-
|
266
|
-
@ready = true
|
283
|
+
set_ready
|
267
284
|
return true
|
268
285
|
end
|
269
286
|
|
@@ -291,15 +308,14 @@ module Puma
|
|
291
308
|
data = @io.read_nonblock(CHUNK_SIZE)
|
292
309
|
rescue Errno::EAGAIN
|
293
310
|
return false
|
294
|
-
rescue SystemCallError, IOError
|
311
|
+
rescue SystemCallError, IOError, EOFError
|
295
312
|
raise ConnectionError, "Connection error detected during read"
|
296
313
|
end
|
297
314
|
|
298
315
|
# No data means a closed socket
|
299
316
|
unless data
|
300
317
|
@buffer = nil
|
301
|
-
|
302
|
-
@ready = true
|
318
|
+
set_ready
|
303
319
|
raise EOFError
|
304
320
|
end
|
305
321
|
|
@@ -335,8 +351,7 @@ module Puma
|
|
335
351
|
# No data means a closed socket
|
336
352
|
unless data
|
337
353
|
@buffer = nil
|
338
|
-
|
339
|
-
@ready = true
|
354
|
+
set_ready
|
340
355
|
raise EOFError
|
341
356
|
end
|
342
357
|
|
@@ -413,8 +428,7 @@ module Puma
|
|
413
428
|
unless chunk
|
414
429
|
@body.close
|
415
430
|
@buffer = nil
|
416
|
-
|
417
|
-
@ready = true
|
431
|
+
set_ready
|
418
432
|
raise EOFError
|
419
433
|
end
|
420
434
|
|
@@ -423,8 +437,7 @@ module Puma
|
|
423
437
|
if remain <= 0
|
424
438
|
@body.rewind
|
425
439
|
@buffer = nil
|
426
|
-
|
427
|
-
@ready = true
|
440
|
+
set_ready
|
428
441
|
return true
|
429
442
|
end
|
430
443
|
|
@@ -433,6 +446,14 @@ module Puma
|
|
433
446
|
false
|
434
447
|
end
|
435
448
|
|
449
|
+
def set_ready
|
450
|
+
if @body_read_start
|
451
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
|
452
|
+
end
|
453
|
+
@requests_served += 1
|
454
|
+
@ready = true
|
455
|
+
end
|
456
|
+
|
436
457
|
def write_400
|
437
458
|
begin
|
438
459
|
@io << ERROR_400_RESPONSE
|
data/lib/puma/cluster.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/runner'
|
2
4
|
require 'puma/util'
|
3
5
|
require 'puma/plugin'
|
@@ -17,8 +19,6 @@ module Puma
|
|
17
19
|
# via the `spawn_workers` method call. Each worker will have it's own
|
18
20
|
# instance of a `Puma::Server`.
|
19
21
|
class Cluster < Runner
|
20
|
-
WORKER_CHECK_INTERVAL = 5
|
21
|
-
|
22
22
|
def initialize(cli, events)
|
23
23
|
super cli, events
|
24
24
|
|
@@ -35,7 +35,35 @@ module Puma
|
|
35
35
|
@workers.each { |x| x.term }
|
36
36
|
|
37
37
|
begin
|
38
|
-
|
38
|
+
if RUBY_VERSION < '2.6'
|
39
|
+
@workers.each do |w|
|
40
|
+
begin
|
41
|
+
Process.waitpid(w.pid)
|
42
|
+
rescue Errno::ECHILD
|
43
|
+
# child is already terminated
|
44
|
+
end
|
45
|
+
end
|
46
|
+
else
|
47
|
+
# below code is for a bug in Ruby 2.6+, above waitpid call hangs
|
48
|
+
t_st = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
49
|
+
pids = @workers.map(&:pid)
|
50
|
+
loop do
|
51
|
+
pids.reject! do |w_pid|
|
52
|
+
begin
|
53
|
+
if Process.waitpid(w_pid, Process::WNOHANG)
|
54
|
+
log " worker status: #{$?}"
|
55
|
+
true
|
56
|
+
end
|
57
|
+
rescue Errno::ECHILD
|
58
|
+
true # child is already terminated
|
59
|
+
end
|
60
|
+
end
|
61
|
+
break if pids.empty?
|
62
|
+
sleep 0.5
|
63
|
+
end
|
64
|
+
t_end = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
65
|
+
log format(" worker shutdown time: %6.2f", t_end - t_st)
|
66
|
+
end
|
39
67
|
rescue Interrupt
|
40
68
|
log "! Cancelled waiting for workers"
|
41
69
|
end
|
@@ -181,7 +209,7 @@ module Puma
|
|
181
209
|
def check_workers(force=false)
|
182
210
|
return if !force && @next_check && @next_check >= Time.now
|
183
211
|
|
184
|
-
@next_check = Time.now + WORKER_CHECK_INTERVAL
|
212
|
+
@next_check = Time.now + Const::WORKER_CHECK_INTERVAL
|
185
213
|
|
186
214
|
any = false
|
187
215
|
|
@@ -198,14 +226,9 @@ module Puma
|
|
198
226
|
# during this loop by giving the kernel time to kill them.
|
199
227
|
sleep 1 if any
|
200
228
|
|
201
|
-
|
202
|
-
pid = Process.waitpid(-1, Process::WNOHANG)
|
203
|
-
break unless pid
|
229
|
+
@workers.reject! { |w| Process.waitpid(w.pid, Process::WNOHANG) }
|
204
230
|
|
205
|
-
|
206
|
-
end
|
207
|
-
|
208
|
-
@workers.delete_if(&:dead?)
|
231
|
+
@workers.reject!(&:dead?)
|
209
232
|
|
210
233
|
cull_workers
|
211
234
|
spawn_workers
|
@@ -288,7 +311,7 @@ module Puma
|
|
288
311
|
base_payload = "p#{Process.pid}"
|
289
312
|
|
290
313
|
while true
|
291
|
-
sleep WORKER_CHECK_INTERVAL
|
314
|
+
sleep Const::WORKER_CHECK_INTERVAL
|
292
315
|
begin
|
293
316
|
b = server.backlog || 0
|
294
317
|
r = server.running || 0
|
@@ -388,10 +411,13 @@ module Puma
|
|
388
411
|
log "Early termination of worker"
|
389
412
|
exit! 0
|
390
413
|
else
|
414
|
+
@launcher.close_binder_listeners
|
415
|
+
|
391
416
|
stop_workers
|
392
417
|
stop
|
393
418
|
|
394
|
-
raise
|
419
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
420
|
+
exit 0 # Clean exit, workers were stopped
|
395
421
|
end
|
396
422
|
end
|
397
423
|
end
|
@@ -485,7 +511,7 @@ module Puma
|
|
485
511
|
|
486
512
|
force_check = false
|
487
513
|
|
488
|
-
res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
|
514
|
+
res = IO.select([read], nil, nil, Const::WORKER_CHECK_INTERVAL)
|
489
515
|
|
490
516
|
if res
|
491
517
|
req = read.read_nonblock(1)
|