puma 3.12.6 → 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.

@@ -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)
@@ -50,7 +50,13 @@ module Puma
50
50
 
51
51
  def close
52
52
  @ios.each { |i| i.close }
53
- @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
54
60
  end
55
61
 
56
62
  def import_from_env
@@ -187,6 +193,8 @@ module Puma
187
193
  ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
188
194
  end
189
195
 
196
+ ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
197
+
190
198
  if params['verify_mode']
191
199
  ctx.verify_mode = case params['verify_mode']
192
200
  when "peer"
@@ -27,9 +27,10 @@ module Puma
27
27
  # For example a web request from a browser or from CURL. This
28
28
  #
29
29
  # An instance of `Puma::Client` can be used as if it were an IO object
30
- # for example it is passed into `IO.select` inside of the `Puma::Reactor`.
31
- # This is accomplished by the `to_io` method which gets called on any
32
- # 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.
33
34
  #
34
35
  # Instances of this class are responsible for knowing if
35
36
  # the header and body are fully buffered via the `try_to_finish` method.
@@ -54,6 +55,7 @@ module Puma
54
55
  @ready = false
55
56
 
56
57
  @body = nil
58
+ @body_read_start = nil
57
59
  @buffer = nil
58
60
  @tempfile = nil
59
61
 
@@ -64,6 +66,8 @@ module Puma
64
66
 
65
67
  @peerip = nil
66
68
  @remote_addr_header = nil
69
+
70
+ @body_remain = 0
67
71
  end
68
72
 
69
73
  attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
@@ -102,6 +106,8 @@ module Puma
102
106
  @tempfile = nil
103
107
  @parsed_bytes = 0
104
108
  @ready = false
109
+ @body_remain = 0
110
+ @peerip = nil
105
111
 
106
112
  if @buffer
107
113
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
@@ -114,9 +120,16 @@ module Puma
114
120
  end
115
121
 
116
122
  return false
117
- elsif fast_check &&
118
- IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
119
- 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
+
120
133
  end
121
134
  end
122
135
 
@@ -147,8 +160,11 @@ module Puma
147
160
  def decode_chunk(chunk)
148
161
  if @partial_part_left > 0
149
162
  if @partial_part_left <= chunk.size
150
- @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
151
166
  chunk = chunk[@partial_part_left..-1]
167
+ @partial_part_left = 0
152
168
  else
153
169
  @body << chunk
154
170
  @partial_part_left -= chunk.size
@@ -172,8 +188,7 @@ module Puma
172
188
  rest = io.read
173
189
  rest = rest[2..-1] if rest.start_with?("\r\n")
174
190
  @buffer = rest.empty? ? nil : rest
175
- @requests_served += 1
176
- @ready = true
191
+ set_ready
177
192
  return true
178
193
  end
179
194
 
@@ -211,7 +226,7 @@ module Puma
211
226
  while true
212
227
  begin
213
228
  chunk = @io.read_nonblock(4096)
214
- rescue Errno::EAGAIN
229
+ rescue IO::WaitReadable
215
230
  return false
216
231
  rescue SystemCallError, IOError
217
232
  raise ConnectionError, "Connection error detected during read"
@@ -221,8 +236,7 @@ module Puma
221
236
  unless chunk
222
237
  @body.close
223
238
  @buffer = nil
224
- @requests_served += 1
225
- @ready = true
239
+ set_ready
226
240
  raise EOFError
227
241
  end
228
242
 
@@ -231,6 +245,8 @@ module Puma
231
245
  end
232
246
 
233
247
  def setup_body
248
+ @body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
249
+
234
250
  if @env[HTTP_EXPECT] == CONTINUE
235
251
  # TODO allow a hook here to check the headers before
236
252
  # going forward
@@ -244,16 +260,8 @@ module Puma
244
260
 
245
261
  te = @env[TRANSFER_ENCODING2]
246
262
 
247
- if te
248
- if te.include?(",")
249
- te.split(",").each do |part|
250
- if CHUNKED.casecmp(part.strip) == 0
251
- return setup_chunked_body(body)
252
- end
253
- end
254
- elsif CHUNKED.casecmp(te) == 0
255
- return setup_chunked_body(body)
256
- end
263
+ if te && CHUNKED.casecmp(te) == 0
264
+ return setup_chunked_body(body)
257
265
  end
258
266
 
259
267
  @chunked_body = false
@@ -263,8 +271,7 @@ module Puma
263
271
  unless cl
264
272
  @buffer = body.empty? ? nil : body
265
273
  @body = EmptyBody
266
- @requests_served += 1
267
- @ready = true
274
+ set_ready
268
275
  return true
269
276
  end
270
277
 
@@ -273,8 +280,7 @@ module Puma
273
280
  if remain <= 0
274
281
  @body = StringIO.new(body)
275
282
  @buffer = nil
276
- @requests_served += 1
277
- @ready = true
283
+ set_ready
278
284
  return true
279
285
  end
280
286
 
@@ -302,15 +308,14 @@ module Puma
302
308
  data = @io.read_nonblock(CHUNK_SIZE)
303
309
  rescue Errno::EAGAIN
304
310
  return false
305
- rescue SystemCallError, IOError
311
+ rescue SystemCallError, IOError, EOFError
306
312
  raise ConnectionError, "Connection error detected during read"
307
313
  end
308
314
 
309
315
  # No data means a closed socket
310
316
  unless data
311
317
  @buffer = nil
312
- @requests_served += 1
313
- @ready = true
318
+ set_ready
314
319
  raise EOFError
315
320
  end
316
321
 
@@ -346,8 +351,7 @@ module Puma
346
351
  # No data means a closed socket
347
352
  unless data
348
353
  @buffer = nil
349
- @requests_served += 1
350
- @ready = true
354
+ set_ready
351
355
  raise EOFError
352
356
  end
353
357
 
@@ -424,8 +428,7 @@ module Puma
424
428
  unless chunk
425
429
  @body.close
426
430
  @buffer = nil
427
- @requests_served += 1
428
- @ready = true
431
+ set_ready
429
432
  raise EOFError
430
433
  end
431
434
 
@@ -434,8 +437,7 @@ module Puma
434
437
  if remain <= 0
435
438
  @body.rewind
436
439
  @buffer = nil
437
- @requests_served += 1
438
- @ready = true
440
+ set_ready
439
441
  return true
440
442
  end
441
443
 
@@ -444,6 +446,14 @@ module Puma
444
446
  false
445
447
  end
446
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
+
447
457
  def write_400
448
458
  begin
449
459
  @io << ERROR_400_RESPONSE
@@ -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,35 @@ module Puma
37
35
  @workers.each { |x| x.term }
38
36
 
39
37
  begin
40
- @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
41
67
  rescue Interrupt
42
68
  log "! Cancelled waiting for workers"
43
69
  end
@@ -183,7 +209,7 @@ module Puma
183
209
  def check_workers(force=false)
184
210
  return if !force && @next_check && @next_check >= Time.now
185
211
 
186
- @next_check = Time.now + WORKER_CHECK_INTERVAL
212
+ @next_check = Time.now + Const::WORKER_CHECK_INTERVAL
187
213
 
188
214
  any = false
189
215
 
@@ -200,14 +226,9 @@ module Puma
200
226
  # during this loop by giving the kernel time to kill them.
201
227
  sleep 1 if any
202
228
 
203
- while @workers.any?
204
- pid = Process.waitpid(-1, Process::WNOHANG)
205
- break unless pid
229
+ @workers.reject! { |w| Process.waitpid(w.pid, Process::WNOHANG) }
206
230
 
207
- @workers.delete_if { |w| w.pid == pid }
208
- end
209
-
210
- @workers.delete_if(&:dead?)
231
+ @workers.reject!(&:dead?)
211
232
 
212
233
  cull_workers
213
234
  spawn_workers
@@ -290,7 +311,7 @@ module Puma
290
311
  base_payload = "p#{Process.pid}"
291
312
 
292
313
  while true
293
- sleep WORKER_CHECK_INTERVAL
314
+ sleep Const::WORKER_CHECK_INTERVAL
294
315
  begin
295
316
  b = server.backlog || 0
296
317
  r = server.running || 0
@@ -390,10 +411,13 @@ module Puma
390
411
  log "Early termination of worker"
391
412
  exit! 0
392
413
  else
414
+ @launcher.close_binder_listeners
415
+
393
416
  stop_workers
394
417
  stop
395
418
 
396
- raise SignalException, "SIGTERM"
419
+ raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
420
+ exit 0 # Clean exit, workers were stopped
397
421
  end
398
422
  end
399
423
  end
@@ -487,7 +511,7 @@ module Puma
487
511
 
488
512
  force_check = false
489
513
 
490
- res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
514
+ res = IO.select([read], nil, nil, Const::WORKER_CHECK_INTERVAL)
491
515
 
492
516
  if res
493
517
  req = read.read_nonblock(1)
@@ -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
 
@@ -100,8 +100,8 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "3.12.6".freeze
104
- CODE_NAME = "Llamas in Pajamas".freeze
103
+ PUMA_VERSION = VERSION = "4.0.0".freeze
104
+ CODE_NAME = "4 Fast 4 Furious".freeze
105
105
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
106
106
 
107
107
  FAST_TRACK_KA_TIMEOUT = 0.2
@@ -118,13 +118,6 @@ module Puma
118
118
  # sending data back
119
119
  WRITE_TIMEOUT = 10
120
120
 
121
- # How many requests to attempt inline before sending a client back to
122
- # the reactor to be subject to normal ordering. The idea here is that
123
- # we amortize the cost of going back to the reactor for a well behaved
124
- # but very "greedy" client across 10 requests. This prevents a not
125
- # well behaved client from monopolizing the thread forever.
126
- MAX_FAST_INLINE = 10
127
-
128
121
  # The original URI requested by the client.
129
122
  REQUEST_URI= 'REQUEST_URI'.freeze
130
123
  REQUEST_PATH = 'REQUEST_PATH'.freeze
@@ -228,12 +221,15 @@ module Puma
228
221
  COLON = ": ".freeze
229
222
 
230
223
  NEWLINE = "\n".freeze
231
- HTTP_INJECTION_REGEX = /[\r\n]/.freeze
232
224
 
233
225
  HIJACK_P = "rack.hijack?".freeze
234
226
  HIJACK = "rack.hijack".freeze
235
227
  HIJACK_IO = "rack.hijack_io".freeze
236
228
 
237
229
  EARLY_HINTS = "rack.early_hints".freeze
230
+
231
+ # Mininum interval to checks worker health
232
+ WORKER_CHECK_INTERVAL = 5
233
+
238
234
  end
239
235
  end
@@ -206,6 +206,16 @@ module Puma
206
206
  when "phased-restart"
207
207
  Process.kill "SIGUSR1", @pid
208
208
 
209
+ when "status"
210
+ begin
211
+ Process.kill 0, @pid
212
+ puts "Puma is started"
213
+ rescue Errno::ESRCH
214
+ raise "Puma is not running"
215
+ end
216
+
217
+ return
218
+
209
219
  else
210
220
  return
211
221
  end