puma 5.1.1 → 5.3.1

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.

Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +131 -10
  3. data/README.md +24 -2
  4. data/docs/architecture.md +22 -18
  5. data/docs/compile_options.md +6 -6
  6. data/docs/deployment.md +2 -2
  7. data/docs/jungle/rc.d/README.md +1 -1
  8. data/docs/kubernetes.md +66 -0
  9. data/docs/plugins.md +2 -2
  10. data/docs/rails_dev_mode.md +29 -0
  11. data/docs/restart.md +1 -1
  12. data/docs/stats.md +142 -0
  13. data/docs/systemd.md +1 -1
  14. data/ext/puma_http11/extconf.rb +14 -0
  15. data/ext/puma_http11/http11_parser.c +19 -21
  16. data/ext/puma_http11/http11_parser.h +1 -1
  17. data/ext/puma_http11/http11_parser.java.rl +1 -1
  18. data/ext/puma_http11/http11_parser.rl +1 -1
  19. data/ext/puma_http11/mini_ssl.c +162 -84
  20. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +5 -7
  21. data/ext/puma_http11/puma_http11.c +2 -2
  22. data/lib/puma.rb +34 -8
  23. data/lib/puma/binder.rb +50 -43
  24. data/lib/puma/client.rb +5 -3
  25. data/lib/puma/cluster.rb +40 -8
  26. data/lib/puma/cluster/worker_handle.rb +4 -0
  27. data/lib/puma/configuration.rb +4 -1
  28. data/lib/puma/const.rb +3 -3
  29. data/lib/puma/control_cli.rb +5 -1
  30. data/lib/puma/detect.rb +14 -10
  31. data/lib/puma/dsl.rb +56 -4
  32. data/lib/puma/error_logger.rb +12 -5
  33. data/lib/puma/events.rb +2 -3
  34. data/lib/puma/launcher.rb +4 -3
  35. data/lib/puma/minissl.rb +48 -17
  36. data/lib/puma/minissl/context_builder.rb +6 -0
  37. data/lib/puma/null_io.rb +12 -0
  38. data/lib/puma/queue_close.rb +7 -7
  39. data/lib/puma/reactor.rb +7 -2
  40. data/lib/puma/request.rb +9 -4
  41. data/lib/puma/runner.rb +8 -3
  42. data/lib/puma/server.rb +46 -112
  43. data/lib/puma/thread_pool.rb +4 -3
  44. data/lib/rack/handler/puma.rb +1 -0
  45. metadata +6 -3
@@ -182,8 +182,6 @@ static final int puma_parser_start = 1;
182
182
  static final int puma_parser_first_final = 46;
183
183
  static final int puma_parser_error = 0;
184
184
 
185
- static final int puma_parser_en_main = 1;
186
-
187
185
 
188
186
  // line 62 "ext/puma_http11/http11_parser.java.rl"
189
187
 
@@ -212,12 +210,12 @@ static final int puma_parser_en_main = 1;
212
210
  cs = 0;
213
211
 
214
212
 
215
- // line 218 "ext/puma_http11/org/jruby/puma/Http11Parser.java"
213
+ // line 214 "ext/puma_http11/org/jruby/puma/Http11Parser.java"
216
214
  {
217
215
  cs = puma_parser_start;
218
216
  }
219
217
 
220
- // line 90 "ext/puma_http11/http11_parser.java.rl"
218
+ // line 88 "ext/puma_http11/http11_parser.java.rl"
221
219
 
222
220
  body_start = 0;
223
221
  content_len = 0;
@@ -244,7 +242,7 @@ static final int puma_parser_en_main = 1;
244
242
  parser.buffer = buffer;
245
243
 
246
244
 
247
- // line 250 "ext/puma_http11/org/jruby/puma/Http11Parser.java"
245
+ // line 246 "ext/puma_http11/org/jruby/puma/Http11Parser.java"
248
246
  {
249
247
  int _klen;
250
248
  int _trans = 0;
@@ -400,7 +398,7 @@ case 1:
400
398
  { p += 1; _goto_targ = 5; if (true) continue _goto;}
401
399
  }
402
400
  break;
403
- // line 406 "ext/puma_http11/org/jruby/puma/Http11Parser.java"
401
+ // line 402 "ext/puma_http11/org/jruby/puma/Http11Parser.java"
404
402
  }
405
403
  }
406
404
  }
@@ -420,7 +418,7 @@ case 5:
420
418
  break; }
421
419
  }
422
420
 
423
- // line 116 "ext/puma_http11/http11_parser.java.rl"
421
+ // line 114 "ext/puma_http11/http11_parser.java.rl"
424
422
 
425
423
  parser.cs = cs;
426
424
  parser.nread += (p - off);
@@ -41,8 +41,8 @@ static VALUE global_request_path;
41
41
 
42
42
  /** Defines common length and error messages for input length validation. */
43
43
  #define QUOTE(s) #s
44
- #define EXPLAND_MAX_LENGHT_VALUE(s) QUOTE(s)
45
- #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " EXPLAND_MAX_LENGHT_VALUE(length) " allowed length (was %d)"
44
+ #define EXPLAIN_MAX_LENGTH_VALUE(s) QUOTE(s)
45
+ #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " EXPLAIN_MAX_LENGTH_VALUE(length) " allowed length (was %d)"
46
46
 
47
47
  /** Validates the max length of given input and throws an HttpParserError exception if over. */
48
48
  #define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR, len); }
data/lib/puma.rb CHANGED
@@ -19,6 +19,40 @@ module Puma
19
19
  autoload :Server, 'puma/server'
20
20
  autoload :Launcher, 'puma/launcher'
21
21
 
22
+ # at present, MiniSSL::Engine is only defined in extension code (puma_http11),
23
+ # not in minissl.rb
24
+ HAS_SSL = const_defined?(:MiniSSL, false) && MiniSSL.const_defined?(:Engine, false)
25
+
26
+ HAS_UNIX_SOCKET = Object.const_defined? :UNIXSocket
27
+
28
+ if HAS_SSL
29
+ require 'puma/minissl'
30
+ else
31
+ module MiniSSL
32
+ # this class is defined so that it exists when Puma is compiled
33
+ # without ssl support, as Server and Reactor use it in rescue statements.
34
+ class SSLError < StandardError ; end
35
+ end
36
+ end
37
+
38
+ def self.ssl?
39
+ HAS_SSL
40
+ end
41
+
42
+ def self.abstract_unix_socket?
43
+ @abstract_unix ||=
44
+ if HAS_UNIX_SOCKET
45
+ begin
46
+ ::UNIXServer.new("\0puma.temp.unix").close
47
+ true
48
+ rescue ArgumentError # darwin
49
+ false
50
+ end
51
+ else
52
+ false
53
+ end
54
+ end
55
+
22
56
  # @!attribute [rw] stats_object=
23
57
  def self.stats_object=(val)
24
58
  @get_stats = val
@@ -40,12 +74,4 @@ module Puma
40
74
  return unless Thread.current.respond_to?(:name=)
41
75
  Thread.current.name = "puma #{name}"
42
76
  end
43
-
44
- unless HAS_SSL
45
- module MiniSSL
46
- # this class is defined so that it exists when Puma is compiled
47
- # without ssl support, as Server and Reactor use it in rescue statements.
48
- class SSLError < StandardError ; end
49
- end
50
- end
51
77
  end
data/lib/puma/binder.rb CHANGED
@@ -13,7 +13,7 @@ module Puma
13
13
  require 'puma/minissl'
14
14
  require 'puma/minissl/context_builder'
15
15
 
16
- # Odd bug in 'pure Ruby' nio4r verion 2.5.2, which installs with Ruby 2.3.
16
+ # Odd bug in 'pure Ruby' nio4r version 2.5.2, which installs with Ruby 2.3.
17
17
  # NIO doesn't create any OpenSSL objects, but it rescues an OpenSSL error.
18
18
  # The bug was that it did not require openssl.
19
19
  # @todo remove when Ruby 2.3 support is dropped
@@ -160,6 +160,7 @@ module Puma
160
160
  io = inherit_tcp_listener uri.host, uri.port, sock
161
161
  logger.log "* Activated #{str}"
162
162
  else
163
+ ios_len = @ios.length
163
164
  params = Util.parse_query uri.query
164
165
 
165
166
  opt = params.key?('low_latency')
@@ -167,14 +168,8 @@ module Puma
167
168
 
168
169
  io = add_tcp_listener uri.host, uri.port, opt, bak
169
170
 
170
- @ios.each do |i|
171
- next unless TCPServer === i
172
- addr = if i.local_address.ipv6?
173
- "[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
174
- else
175
- i.local_address.ip_unpack.join(':')
176
- end
177
-
171
+ @ios[ios_len..-1].each do |i|
172
+ addr = loc_addr_str i
178
173
  logger.log "* #{log_msg} on http://#{addr}"
179
174
  end
180
175
  end
@@ -182,11 +177,19 @@ module Puma
182
177
  @listeners << [str, io] if io
183
178
  when "unix"
184
179
  path = "#{uri.host}#{uri.path}".gsub("%20", " ")
180
+ abstract = false
181
+ if str.start_with? 'unix://@'
182
+ raise "OS does not support abstract UNIXSockets" unless Puma.abstract_unix_socket?
183
+ abstract = true
184
+ path = "@#{path}"
185
+ end
185
186
 
186
187
  if fd = @inherited_fds.delete(str)
188
+ @unix_paths << path unless abstract
187
189
  io = inherit_unix_listener path, fd
188
190
  logger.log "* Inherited #{str}"
189
191
  elsif sock = @activated_sockets.delete([ :unix, path ])
192
+ @unix_paths << path unless abstract || File.exist?(path)
190
193
  io = inherit_unix_listener path, sock
191
194
  logger.log "* Activated #{str}"
192
195
  else
@@ -210,6 +213,7 @@ module Puma
210
213
  end
211
214
  end
212
215
 
216
+ @unix_paths << path unless abstract || File.exist?(path)
213
217
  io = add_unix_listener path, umask, mode, backlog
214
218
  logger.log "* #{log_msg} on #{str}"
215
219
  end
@@ -229,8 +233,13 @@ module Puma
229
233
  io = inherit_ssl_listener sock, ctx
230
234
  logger.log "* Activated #{str}"
231
235
  else
236
+ ios_len = @ios.length
232
237
  io = add_ssl_listener uri.host, uri.port, ctx
233
- logger.log "* Listening on #{str}"
238
+
239
+ @ios[ios_len..-1].each do |i|
240
+ addr = loc_addr_str i
241
+ logger.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
242
+ end
234
243
  end
235
244
 
236
245
  @listeners << [str, io] if io
@@ -258,14 +267,18 @@ module Puma
258
267
  end
259
268
 
260
269
  # Also close any unused activated sockets
261
- @activated_sockets.each do |key, sock|
262
- logger.log "* Closing unused activated socket: #{key.join ':'}"
263
- begin
264
- sock.close
265
- rescue SystemCallError
270
+ unless @activated_sockets.empty?
271
+ fds = @ios.map(&:to_i)
272
+ @activated_sockets.each do |key, sock|
273
+ next if fds.include? sock.to_i
274
+ logger.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
275
+ begin
276
+ sock.close
277
+ rescue SystemCallError
278
+ end
279
+ # We have to unlink a unix socket path that's not being used
280
+ File.unlink key[1] if key.first == :unix
266
281
  end
267
- # We have to unlink a unix socket path that's not being used
268
- File.unlink key[1] if key[0] == :unix
269
282
  end
270
283
  end
271
284
 
@@ -297,11 +310,7 @@ module Puma
297
310
  end
298
311
 
299
312
  def inherit_tcp_listener(host, port, fd)
300
- if fd.kind_of? TCPServer
301
- s = fd
302
- else
303
- s = TCPServer.for_fd(fd)
304
- end
313
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
305
314
 
306
315
  @ios << s
307
316
  s
@@ -339,11 +348,8 @@ module Puma
339
348
  def inherit_ssl_listener(fd, ctx)
340
349
  raise "Puma compiled without SSL support" unless HAS_SSL
341
350
 
342
- if fd.kind_of? TCPServer
343
- s = fd
344
- else
345
- s = TCPServer.for_fd(fd)
346
- end
351
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
352
+
347
353
  ssl = MiniSSL::Server.new(s, ctx)
348
354
 
349
355
  env = @proto_env.dup
@@ -358,8 +364,6 @@ module Puma
358
364
  # Tell the server to listen on +path+ as a UNIX domain socket.
359
365
  #
360
366
  def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
361
- @unix_paths << path unless File.exist? path
362
-
363
367
  # Let anyone connect by default
364
368
  umask ||= 0
365
369
 
@@ -376,8 +380,7 @@ module Puma
376
380
  raise "There is already a server bound to: #{path}"
377
381
  end
378
382
  end
379
-
380
- s = UNIXServer.new(path)
383
+ s = UNIXServer.new path.sub(/\A@/, "\0") # check for abstract UNIXSocket
381
384
  s.listen backlog
382
385
  @ios << s
383
386
  ensure
@@ -396,13 +399,8 @@ module Puma
396
399
  end
397
400
 
398
401
  def inherit_unix_listener(path, fd)
399
- @unix_paths << path unless File.exist? path
402
+ s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
400
403
 
401
- if fd.kind_of? TCPServer
402
- s = fd
403
- else
404
- s = UNIXServer.for_fd fd
405
- end
406
404
  @ios << s
407
405
 
408
406
  env = @proto_env.dup
@@ -413,24 +411,24 @@ module Puma
413
411
  end
414
412
 
415
413
  def close_listeners
416
- listeners.each do |l, io|
417
- io.close unless io.closed? # Ruby 2.2 issue
418
- uri = URI.parse(l)
414
+ @listeners.each do |l, io|
415
+ io.close unless io.closed?
416
+ uri = URI.parse l
419
417
  next unless uri.scheme == 'unix'
420
418
  unix_path = "#{uri.host}#{uri.path}"
421
- File.unlink unix_path if unix_paths.include? unix_path
419
+ File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
422
420
  end
423
421
  end
424
422
 
425
423
  def redirects_for_restart
426
- redirects = listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
424
+ redirects = @listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
427
425
  redirects[:close_others] = true
428
426
  redirects
429
427
  end
430
428
 
431
429
  # @version 5.0.0
432
430
  def redirects_for_restart_env
433
- listeners.each_with_object({}).with_index do |(listen, memo), i|
431
+ @listeners.each_with_object({}).with_index do |(listen, memo), i|
434
432
  memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
435
433
  end
436
434
  end
@@ -444,6 +442,15 @@ module Puma
444
442
  end.map { |addrinfo| addrinfo.ip_address }.uniq
445
443
  end
446
444
 
445
+ def loc_addr_str(io)
446
+ loc_addr = io.to_io.local_address
447
+ if loc_addr.ipv6?
448
+ "[#{loc_addr.ip_unpack[0]}]:#{loc_addr.ip_unpack[1]}"
449
+ else
450
+ loc_addr.ip_unpack.join(':')
451
+ end
452
+ end
453
+
447
454
  # @version 5.0.0
448
455
  def socket_activation_fd(int)
449
456
  int + 3 # 3 is the magic number you add to follow the SA protocol
data/lib/puma/client.rb CHANGED
@@ -126,7 +126,7 @@ module Puma
126
126
  @parsed_bytes = 0
127
127
  @ready = false
128
128
  @body_remain = 0
129
- @peerip = nil
129
+ @peerip = nil if @remote_addr_header
130
130
  @in_last_chunk = false
131
131
 
132
132
  if @buffer
@@ -295,6 +295,7 @@ module Puma
295
295
 
296
296
  if remain > MAX_BODY
297
297
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
298
+ @body.unlink
298
299
  @body.binmode
299
300
  @tempfile = @body
300
301
  else
@@ -374,7 +375,7 @@ module Puma
374
375
  end
375
376
 
376
377
  if decode_chunk(chunk)
377
- @env[CONTENT_LENGTH] = @chunked_content_length
378
+ @env[CONTENT_LENGTH] = @chunked_content_length.to_s
378
379
  return true
379
380
  end
380
381
  end
@@ -386,12 +387,13 @@ module Puma
386
387
  @prev_chunk = ""
387
388
 
388
389
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
390
+ @body.unlink
389
391
  @body.binmode
390
392
  @tempfile = @body
391
393
  @chunked_content_length = 0
392
394
 
393
395
  if decode_chunk(body)
394
- @env[CONTENT_LENGTH] = @chunked_content_length
396
+ @env[CONTENT_LENGTH] = @chunked_content_length.to_s
395
397
  return true
396
398
  end
397
399
  end
data/lib/puma/cluster.rb CHANGED
@@ -43,6 +43,7 @@ module Puma
43
43
  end
44
44
 
45
45
  def start_phased_restart
46
+ @events.fire_on_restart!
46
47
  @phase += 1
47
48
  log "- Starting phased worker restart, phase: #{@phase}"
48
49
 
@@ -317,7 +318,7 @@ module Puma
317
318
 
318
319
  stop_workers
319
320
  stop
320
-
321
+ @events.fire_on_stopped!
321
322
  raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
322
323
  exit 0 # Clean exit, workers were stopped
323
324
  end
@@ -332,16 +333,22 @@ module Puma
332
333
  # This is aligned with the output from Runner, see Runner#output_header
333
334
  log "* Workers: #{@options[:workers]}"
334
335
 
335
- # Threads explicitly marked as fork safe will be ignored.
336
- # Used in Rails, but may be used by anyone.
337
- before = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
338
-
339
336
  if preload?
337
+ # Threads explicitly marked as fork safe will be ignored. Used in Rails,
338
+ # but may be used by anyone. Note that we need to explicit
339
+ # Process::Waiter check here because there's a bug in Ruby 2.6 and below
340
+ # where calling thread_variable_get on a Process::Waiter will segfault.
341
+ # We can drop that clause once those versions of Ruby are no longer
342
+ # supported.
343
+ fork_safe = ->(t) { !t.is_a?(Process::Waiter) && t.thread_variable_get(:fork_safe) }
344
+
345
+ before = Thread.list.reject(&fork_safe)
346
+
340
347
  log "* Restarts: (\u2714) hot (\u2716) phased"
341
348
  log "* Preloading application"
342
349
  load_and_bind
343
350
 
344
- after = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
351
+ after = Thread.list.reject(&fork_safe)
345
352
 
346
353
  if after.size > before.size
347
354
  threads = (after - before)
@@ -382,6 +389,8 @@ module Puma
382
389
 
383
390
  log "Use Ctrl-C to stop"
384
391
 
392
+ single_worker_warning
393
+
385
394
  redirect_io
386
395
 
387
396
  Plugins.fire_background
@@ -403,12 +412,16 @@ module Puma
403
412
 
404
413
  begin
405
414
  booted = false
415
+ in_phased_restart = false
416
+ workers_not_booted = @options[:workers]
406
417
 
407
418
  while @status == :run
408
419
  begin
409
420
  if @phased_restart
410
421
  start_phased_restart
411
422
  @phased_restart = false
423
+ in_phased_restart = true
424
+ workers_not_booted = @options[:workers]
412
425
  end
413
426
 
414
427
  check_workers
@@ -434,8 +447,9 @@ module Puma
434
447
  case req
435
448
  when "b"
436
449
  w.boot!
437
- log "- Worker #{w.index} (PID: #{pid}) booted, phase: #{w.phase}"
450
+ log "- Worker #{w.index} (PID: #{pid}) booted in #{w.uptime.round(2)}s, phase: #{w.phase}"
438
451
  @next_check = Time.now
452
+ workers_not_booted -= 1
439
453
  when "e"
440
454
  # external term, see worker method, Signal.trap "SIGTERM"
441
455
  w.instance_variable_set :@term, true
@@ -453,6 +467,10 @@ module Puma
453
467
  log "! Out-of-sync worker list, no #{pid} worker"
454
468
  end
455
469
  end
470
+ if in_phased_restart && workers_not_booted.zero?
471
+ @events.fire_on_booted!
472
+ in_phased_restart = false
473
+ end
456
474
 
457
475
  rescue Interrupt
458
476
  @status = :stop
@@ -470,6 +488,15 @@ module Puma
470
488
 
471
489
  private
472
490
 
491
+ def single_worker_warning
492
+ return if @options[:workers] != 1 || @options[:silence_single_worker_warning]
493
+
494
+ log "! WARNING: Detected running cluster mode with 1 worker."
495
+ log "! Running Puma in cluster mode with a single worker is often a misconfiguration."
496
+ log "! Consider running Puma in single-mode (workers = 0) in order to reduce memory overhead."
497
+ log "! Set the `silence_single_worker_warning` option to silence this warning message."
498
+ end
499
+
473
500
  # loops thru @workers, removing workers that exited, and calling
474
501
  # `#term` if needed
475
502
  def wait_workers
@@ -499,7 +526,12 @@ module Puma
499
526
  def timeout_workers
500
527
  @workers.each do |w|
501
528
  if !w.term? && w.ping_timeout <= Time.now
502
- log "! Terminating timed out worker: #{w.pid}"
529
+ details = if w.booted?
530
+ "(worker failed to check in within #{@options[:worker_timeout]} seconds)"
531
+ else
532
+ "(worker failed to boot within #{@options[:worker_boot_timeout]} seconds)"
533
+ end
534
+ log "! Terminating timed out worker #{details}: #{w.pid}"
503
535
  w.kill
504
536
  end
505
537
  end