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.

Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +36 -0
  3. data/README.md +29 -9
  4. data/docs/architecture.md +1 -0
  5. data/docs/deployment.md +24 -4
  6. data/docs/restart.md +4 -2
  7. data/docs/systemd.md +27 -9
  8. data/ext/puma_http11/PumaHttp11Service.java +2 -0
  9. data/ext/puma_http11/mini_ssl.c +32 -4
  10. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  11. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +11 -4
  12. data/lib/puma/app/status.rb +3 -2
  13. data/lib/puma/binder.rb +19 -10
  14. data/lib/puma/cli.rb +2 -0
  15. data/lib/puma/client.rb +46 -25
  16. data/lib/puma/cluster.rb +40 -14
  17. data/lib/puma/commonlogger.rb +2 -0
  18. data/lib/puma/configuration.rb +4 -1
  19. data/lib/puma/const.rb +8 -2
  20. data/lib/puma/control_cli.rb +21 -9
  21. data/lib/puma/convenient.rb +2 -0
  22. data/lib/puma/daemon_ext.rb +2 -0
  23. data/lib/puma/delegation.rb +2 -0
  24. data/lib/puma/detect.rb +2 -0
  25. data/lib/puma/dsl.rb +57 -5
  26. data/lib/puma/events.rb +2 -0
  27. data/lib/puma/io_buffer.rb +3 -6
  28. data/lib/puma/jruby_restart.rb +2 -0
  29. data/lib/puma/launcher.rb +14 -13
  30. data/lib/puma/minissl.rb +15 -1
  31. data/lib/puma/null_io.rb +2 -0
  32. data/lib/puma/plugin.rb +2 -0
  33. data/lib/puma/rack/builder.rb +2 -1
  34. data/lib/puma/reactor.rb +106 -53
  35. data/lib/puma/runner.rb +3 -1
  36. data/lib/puma/server.rb +27 -24
  37. data/lib/puma/single.rb +4 -2
  38. data/lib/puma/state_file.rb +2 -0
  39. data/lib/puma/tcp_logger.rb +2 -0
  40. data/lib/puma/thread_pool.rb +7 -1
  41. data/lib/puma/util.rb +2 -6
  42. data/lib/rack/handler/puma.rb +3 -0
  43. data/tools/jungle/init.d/puma +5 -5
  44. metadata +19 -8
  45. data/lib/puma/compat.rb +0 -14
  46. data/lib/puma/java_io_buffer.rb +0 -45
  47. 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
- buffer.flip();
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
- buffer.flip();
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 = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
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
 
@@ -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
- json = "{" + GC.stat.map { |k, v| "\"#{k}\": #{v}" }.join(",") + "}"
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 { |i| File.unlink i }
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
4
  require 'uri'
3
5
 
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
- # for example it is passed into `IO.select` inside of the `Puma::Reactor`.
29
- # This is accomplished by the `to_io` method which gets called on any
30
- # non-IO objects being used with the IO api such as `IO.select.
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
- elsif fast_check &&
116
- IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
117
- return try_to_finish
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
- @body << chunk[0..(@partial_part_left-3)] # skip the \r\n
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
- @requests_served += 1
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 Errno::EAGAIN
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
- @requests_served += 1
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
- @requests_served += 1
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
- @requests_served += 1
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
- @requests_served += 1
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
- @requests_served += 1
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
- @requests_served += 1
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
- @requests_served += 1
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
- @workers.each { |w| Process.waitpid(w.pid) }
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
- while @workers.any?
202
- pid = Process.waitpid(-1, Process::WNOHANG)
203
- break unless pid
229
+ @workers.reject! { |w| Process.waitpid(w.pid, Process::WNOHANG) }
204
230
 
205
- @workers.delete_if { |w| w.pid == pid }
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 SignalException, "SIGTERM"
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)