puma 6.4.3 → 6.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -26,14 +26,14 @@ public class Http11 extends RubyObject {
26
26
  public final static String MAX_FIELD_NAME_LENGTH_ERR = "HTTP element FIELD_NAME is longer than the 256 allowed length.";
27
27
  public final static int MAX_FIELD_VALUE_LENGTH = 80 * 1024;
28
28
  public final static String MAX_FIELD_VALUE_LENGTH_ERR = "HTTP element FIELD_VALUE is longer than the 81920 allowed length.";
29
- public final static int MAX_REQUEST_URI_LENGTH = 1024 * 12;
30
- public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the 12288 allowed length.";
29
+ public final static int MAX_REQUEST_URI_LENGTH = getConstLength("PUMA_REQUEST_URI_MAX_LENGTH", 1024 * 12);
30
+ public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the " + MAX_REQUEST_URI_LENGTH + " allowed length.";
31
31
  public final static int MAX_FRAGMENT_LENGTH = 1024;
32
32
  public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 1024 allowed length.";
33
- public final static int MAX_REQUEST_PATH_LENGTH = 8192;
34
- public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 8192 allowed length.";
35
- public final static int MAX_QUERY_STRING_LENGTH = 1024 * 10;
36
- public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the 10240 allowed length.";
33
+ public final static int MAX_REQUEST_PATH_LENGTH = getConstLength("PUMA_REQUEST_PATH_MAX_LENGTH", 8192);
34
+ public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the " + MAX_REQUEST_PATH_LENGTH + " allowed length.";
35
+ public final static int MAX_QUERY_STRING_LENGTH = getConstLength("PUMA_QUERY_STRING_MAX_LENGTH", 10 * 1024);
36
+ public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the " + MAX_QUERY_STRING_LENGTH +" allowed length.";
37
37
  public final static int MAX_HEADER_LENGTH = 1024 * (80 + 32);
38
38
  public final static String MAX_HEADER_LENGTH_ERR = "HTTP element HEADER is longer than the 114688 allowed length.";
39
39
 
@@ -48,6 +48,27 @@ public class Http11 extends RubyObject {
48
48
  public static final ByteList QUERY_STRING_BYTELIST = new ByteList(ByteList.plain("QUERY_STRING"));
49
49
  public static final ByteList SERVER_PROTOCOL_BYTELIST = new ByteList(ByteList.plain("SERVER_PROTOCOL"));
50
50
 
51
+ public static String getEnvOrProperty(String name) {
52
+ String envValue = System.getenv(name);
53
+ return (envValue != null) ? envValue : System.getProperty(name);
54
+ }
55
+
56
+ public static int getConstLength(String name, Integer defaultValue) {
57
+ String stringValue = getEnvOrProperty(name);
58
+ if (stringValue == null || stringValue.isEmpty()) return defaultValue;
59
+
60
+ try {
61
+ int value = Integer.parseUnsignedInt(stringValue);
62
+ if (value <= 0) {
63
+ throw new NumberFormatException("The number is not positive.");
64
+ }
65
+ return value;
66
+ } catch (NumberFormatException e) {
67
+ System.err.println(String.format("The value %s for %s is invalid. Using default value %d instead.", stringValue, name, defaultValue));
68
+ return defaultValue;
69
+ }
70
+ }
71
+
51
72
  private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
52
73
  public IRubyObject allocate(Ruby runtime, RubyClass klass) {
53
74
  return new Http11(runtime, klass);
@@ -56,7 +77,7 @@ public class Http11 extends RubyObject {
56
77
 
57
78
  public static void createHttp11(Ruby runtime) {
58
79
  RubyModule mPuma = runtime.defineModule("Puma");
59
- mPuma.defineClassUnder("HttpParserError",runtime.getClass("IOError"),runtime.getClass("IOError").getAllocator());
80
+ mPuma.defineClassUnder("HttpParserError",runtime.getClass("StandardError"),runtime.getClass("StandardError").getAllocator());
60
81
 
61
82
  RubyClass cHttpParser = mPuma.defineClassUnder("HttpParser",runtime.getObject(),ALLOCATOR);
62
83
  cHttpParser.defineAnnotatedMethods(Http11.class);
@@ -461,6 +461,9 @@ void Init_mini_ssl(VALUE mod);
461
461
 
462
462
  void Init_puma_http11(void)
463
463
  {
464
+ #ifdef HAVE_RB_EXT_RACTOR_SAFE
465
+ rb_ext_ractor_safe(true);
466
+ #endif
464
467
 
465
468
  VALUE mPuma = rb_define_module("Puma");
466
469
  VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
@@ -472,7 +475,7 @@ void Init_puma_http11(void)
472
475
  DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
473
476
  DEF_GLOBAL(request_path, "REQUEST_PATH");
474
477
 
475
- eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eIOError);
478
+ eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eStandardError);
476
479
  rb_global_variable(&eHttpParserError);
477
480
 
478
481
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
@@ -80,7 +80,7 @@ module Puma
80
80
 
81
81
  def authenticate(env)
82
82
  return true unless @auth_token
83
- env['QUERY_STRING'].to_s.split('&;').include? "token=#{@auth_token}"
83
+ env['QUERY_STRING'].to_s.split(/[&;]/).include? "token=#{@auth_token}"
84
84
  end
85
85
 
86
86
  def rack_response(status, body, content_type='application/json')
data/lib/puma/binder.rb CHANGED
@@ -19,13 +19,14 @@ module Puma
19
19
 
20
20
  RACK_VERSION = [1,6].freeze
21
21
 
22
- def initialize(log_writer, conf = Configuration.new)
22
+ def initialize(log_writer, conf = Configuration.new, env: ENV)
23
23
  @log_writer = log_writer
24
24
  @conf = conf
25
25
  @listeners = []
26
26
  @inherited_fds = {}
27
27
  @activated_sockets = {}
28
28
  @unix_paths = []
29
+ @env = env
29
30
 
30
31
  @proto_env = {
31
32
  "rack.version".freeze => RACK_VERSION,
@@ -34,7 +35,7 @@ module Puma
34
35
  "rack.multiprocess".freeze => conf.options[:workers] >= 1,
35
36
  "rack.run_once".freeze => false,
36
37
  RACK_URL_SCHEME => conf.options[:rack_url_scheme],
37
- "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
38
+ "SCRIPT_NAME".freeze => env['SCRIPT_NAME'] || "",
38
39
 
39
40
  # I'd like to set a default CONTENT_TYPE here but some things
40
41
  # depend on their not being a default set and inferring
@@ -87,7 +88,7 @@ module Puma
87
88
  # @version 5.0.0
88
89
  #
89
90
  def create_activated_fds(env_hash)
90
- @log_writer.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
91
+ @log_writer.debug "ENV['LISTEN_FDS'] #{@env['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
91
92
  return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
92
93
  env_hash['LISTEN_FDS'].to_i.times do |index|
93
94
  sock = TCPServer.for_fd(socket_activation_fd(index))
@@ -141,7 +142,14 @@ module Puma
141
142
  end
142
143
  end
143
144
 
145
+ def before_parse(&block)
146
+ @before_parse ||= []
147
+ @before_parse << block if block
148
+ @before_parse
149
+ end
150
+
144
151
  def parse(binds, log_writer = nil, log_msg = 'Listening')
152
+ before_parse.each(&:call)
145
153
  log_writer ||= @log_writer
146
154
  binds.each do |str|
147
155
  uri = URI.parse str
@@ -183,7 +191,7 @@ module Puma
183
191
  io = inherit_unix_listener path, fd
184
192
  log_writer.log "* Inherited #{str}"
185
193
  elsif sock = @activated_sockets.delete([ :unix, path ]) ||
186
- @activated_sockets.delete([ :unix, File.realdirpath(path) ])
194
+ !abstract && @activated_sockets.delete([ :unix, File.realdirpath(path) ])
187
195
  @unix_paths << path unless abstract || File.exist?(path)
188
196
  io = inherit_unix_listener path, sock
189
197
  log_writer.log "* Activated #{str}"
data/lib/puma/cli.rb CHANGED
@@ -24,7 +24,7 @@ module Puma
24
24
  # Create a new CLI object using +argv+ as the command line
25
25
  # arguments.
26
26
  #
27
- def initialize(argv, log_writer = LogWriter.stdio, events = Events.new)
27
+ def initialize(argv, log_writer = LogWriter.stdio, events = Events.new, env: ENV)
28
28
  @debug = false
29
29
  @argv = argv.dup
30
30
  @log_writer = log_writer
@@ -39,7 +39,7 @@ module Puma
39
39
  @control_url = nil
40
40
  @control_options = {}
41
41
 
42
- setup_options
42
+ setup_options env
43
43
 
44
44
  begin
45
45
  @parser.parse! @argv
@@ -63,7 +63,7 @@ module Puma
63
63
  end
64
64
  end
65
65
 
66
- @launcher = Puma::Launcher.new(@conf, :log_writer => @log_writer, :events => @events, :argv => argv)
66
+ @launcher = Puma::Launcher.new(@conf, env: ENV, log_writer: @log_writer, events: @events, argv: argv)
67
67
  end
68
68
 
69
69
  attr_reader :launcher
@@ -92,8 +92,8 @@ module Puma
92
92
  # Build the OptionParser object to handle the available options.
93
93
  #
94
94
 
95
- def setup_options
96
- @conf = Configuration.new({}, {events: @events}) do |user_config, file_config|
95
+ def setup_options(env = ENV)
96
+ @conf = Configuration.new({}, {events: @events}, env) do |user_config, file_config|
97
97
  @parser = OptionParser.new do |o|
98
98
  o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
99
99
  user_config.bind arg
@@ -157,6 +157,10 @@ module Puma
157
157
  user_config.pidfile arg
158
158
  end
159
159
 
160
+ o.on "--plugin PLUGIN", "Load the given PLUGIN. Can be used multiple times to load multiple plugins." do |arg|
161
+ user_config.plugin arg
162
+ end
163
+
160
164
  o.on "--preload", "Preload the app. Cluster mode only" do
161
165
  user_config.preload_app!
162
166
  end
data/lib/puma/client.rb CHANGED
@@ -160,8 +160,6 @@ module Puma
160
160
  @read_header = true
161
161
  @read_proxy = !!@expect_proxy_proto
162
162
  @env = @proto_env.dup
163
- @body = nil
164
- @tempfile = nil
165
163
  @parsed_bytes = 0
166
164
  @ready = false
167
165
  @body_remain = 0
@@ -190,11 +188,11 @@ module Puma
190
188
  rescue IOError
191
189
  # swallow it
192
190
  end
193
-
194
191
  end
195
192
  end
196
193
 
197
194
  def close
195
+ tempfile_close
198
196
  begin
199
197
  @io.close
200
198
  rescue IOError, Errno::EBADF
@@ -202,6 +200,15 @@ module Puma
202
200
  end
203
201
  end
204
202
 
203
+ def tempfile_close
204
+ tf_path = @tempfile&.path
205
+ @tempfile&.close
206
+ File.unlink(tf_path) if tf_path
207
+ @tempfile = nil
208
+ @body = nil
209
+ rescue Errno::ENOENT, IOError
210
+ end
211
+
205
212
  # If necessary, read the PROXY protocol from the buffer. Returns
206
213
  # false if more data is needed.
207
214
  def try_to_parse_proxy_protocol
@@ -240,6 +247,7 @@ module Puma
240
247
 
241
248
  return read_body if in_data_phase
242
249
 
250
+ data = nil
243
251
  begin
244
252
  data = @io.read_nonblock(CHUNK_SIZE)
245
253
  rescue IO::WaitReadable
@@ -403,18 +411,33 @@ module Puma
403
411
  return true
404
412
  end
405
413
 
406
- remain = cl.to_i - body.bytesize
414
+ content_length = cl.to_i
415
+
416
+ remain = content_length - body.bytesize
407
417
 
408
418
  if remain <= 0
409
- @body = StringIO.new(body)
410
- @buffer = nil
419
+ # Part of the body is a pipelined request OR garbage. We'll deal with that later.
420
+ if content_length == 0
421
+ @body = EmptyBody
422
+ if body.empty?
423
+ @buffer = nil
424
+ else
425
+ @buffer = body
426
+ end
427
+ elsif remain == 0
428
+ @body = StringIO.new body
429
+ @buffer = nil
430
+ else
431
+ @body = StringIO.new(body[0,content_length])
432
+ @buffer = body[content_length..-1]
433
+ end
411
434
  set_ready
412
435
  return true
413
436
  end
414
437
 
415
438
  if remain > MAX_BODY
416
- @body = Tempfile.new(Const::PUMA_TMP_BASE)
417
- @body.unlink
439
+ @body = Tempfile.create(Const::PUMA_TMP_BASE)
440
+ File.unlink @body.path unless IS_WINDOWS
418
441
  @body.binmode
419
442
  @tempfile = @body
420
443
  else
@@ -478,7 +501,7 @@ module Puma
478
501
  def read_chunked_body
479
502
  while true
480
503
  begin
481
- chunk = @io.read_nonblock(4096, @read_buffer)
504
+ chunk = @io.read_nonblock(CHUNK_SIZE, @read_buffer)
482
505
  rescue IO::WaitReadable
483
506
  return false
484
507
  rescue SystemCallError, IOError
@@ -506,8 +529,8 @@ module Puma
506
529
  @prev_chunk = ""
507
530
  @excess_cr = 0
508
531
 
509
- @body = Tempfile.new(Const::PUMA_TMP_BASE)
510
- @body.unlink
532
+ @body = Tempfile.create(Const::PUMA_TMP_BASE)
533
+ File.unlink @body.path unless IS_WINDOWS
511
534
  @body.binmode
512
535
  @tempfile = @body
513
536
  @chunked_content_length = 0
@@ -627,7 +650,7 @@ module Puma
627
650
  @partial_part_left = len - part.size
628
651
  end
629
652
  else
630
- if @prev_chunk.size + chunk.size >= MAX_CHUNK_HEADER_SIZE
653
+ if @prev_chunk.size + line.size >= MAX_CHUNK_HEADER_SIZE
631
654
  raise HttpParserError, "maximum size of chunk header exceeded"
632
655
  end
633
656
 
@@ -88,25 +88,27 @@ module Puma
88
88
  server.begin_restart(true)
89
89
  @config.run_hooks(:before_refork, nil, @log_writer, @hook_data)
90
90
  end
91
+ elsif idx == -2 # refork cycle is done
92
+ @config.run_hooks(:after_refork, nil, @log_writer, @hook_data)
91
93
  elsif idx == 0 # restart server
92
94
  restart_server << true << false
93
95
  else # fork worker
94
96
  worker_pids << pid = spawn_worker(idx)
95
- @worker_write << "f#{pid}:#{idx}\n" rescue nil
97
+ @worker_write << "#{PIPE_FORK}#{pid}:#{idx}\n" rescue nil
96
98
  end
97
99
  end
98
100
  end
99
101
  end
100
102
 
101
103
  Signal.trap "SIGTERM" do
102
- @worker_write << "e#{Process.pid}\n" rescue nil
104
+ @worker_write << "#{PIPE_EXTERNAL_TERM}#{Process.pid}\n" rescue nil
103
105
  restart_server.clear
104
106
  server.stop
105
107
  restart_server << false
106
108
  end
107
109
 
108
110
  begin
109
- @worker_write << "b#{Process.pid}:#{index}\n"
111
+ @worker_write << "#{PIPE_BOOT}#{Process.pid}:#{index}\n"
110
112
  rescue SystemCallError, IOError
111
113
  Puma::Util.purge_interrupt_queue
112
114
  STDERR.puts "Master seems to have exited, exiting."
@@ -122,7 +124,7 @@ module Puma
122
124
 
123
125
  stat_thread ||= Thread.new(@worker_write) do |io|
124
126
  Puma.set_thread_name "stat pld"
125
- base_payload = "p#{Process.pid}"
127
+ base_payload = "#{PIPE_PING}#{Process.pid}"
126
128
 
127
129
  while true
128
130
  begin
@@ -131,7 +133,8 @@ module Puma
131
133
  t = server.pool_capacity || 0
132
134
  m = server.max_threads || 0
133
135
  rc = server.requests_count || 0
134
- payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
136
+ bt = server.busy_threads || 0
137
+ payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads":#{m}, "requests_count":#{rc}, "busy_threads":#{bt} }\n!
135
138
  io << payload
136
139
  rescue IOError
137
140
  Puma::Util.purge_interrupt_queue
@@ -147,7 +150,7 @@ module Puma
147
150
  # exiting until any background operations are completed
148
151
  @config.run_hooks(:before_worker_shutdown, index, @log_writer, @hook_data)
149
152
  ensure
150
- @worker_write << "t#{Process.pid}\n" rescue nil
153
+ @worker_write << "#{PIPE_TERM}#{Process.pid}\n" rescue nil
151
154
  @worker_write.close
152
155
  end
153
156
 
@@ -51,13 +51,12 @@ module Puma
51
51
  @term
52
52
  end
53
53
 
54
+ STATUS_PATTERN = /{ "backlog":(?<backlog>\d*), "running":(?<running>\d*), "pool_capacity":(?<pool_capacity>\d*), "max_threads":(?<max_threads>\d*), "requests_count":(?<requests_count>\d*), "busy_threads":(?<busy_threads>\d*) }/
55
+ private_constant :STATUS_PATTERN
56
+
54
57
  def ping!(status)
55
58
  @last_checkin = Time.now
56
- captures = status.match(/{ "backlog":(?<backlog>\d*), "running":(?<running>\d*), "pool_capacity":(?<pool_capacity>\d*), "max_threads": (?<max_threads>\d*), "requests_count": (?<requests_count>\d*) }/)
57
- @last_status = captures.names.inject({}) do |hash, key|
58
- hash[key.to_sym] = captures[key].to_i
59
- hash
60
- end
59
+ @last_status = status.match(STATUS_PATTERN).named_captures.map { |c_name, c| [c_name.to_sym, c.to_i] }.to_h
61
60
  end
62
61
 
63
62
  # @see Puma::Cluster#check_workers
data/lib/puma/cluster.rb CHANGED
@@ -87,6 +87,10 @@ module Puma
87
87
 
88
88
  if @options[:fork_worker] && all_workers_in_phase?
89
89
  @fork_writer << "0\n"
90
+
91
+ if worker_at(0).phase > 0
92
+ @fork_writer << "-2\n"
93
+ end
90
94
  end
91
95
  end
92
96
 
@@ -348,8 +352,6 @@ module Puma
348
352
  def run
349
353
  @status = :run
350
354
 
351
- @idle_workers = {}
352
-
353
355
  output_header "cluster"
354
356
 
355
357
  # This is aligned with the output from Runner, see Runner#output_header
@@ -440,7 +442,7 @@ module Puma
440
442
 
441
443
  while @status == :run
442
444
  begin
443
- if all_workers_idle_timed_out?
445
+ if @options[:idle_timeout] && all_workers_idle_timed_out?
444
446
  log "- All workers reached idle timeout"
445
447
  break
446
448
  end
@@ -456,14 +458,17 @@ module Puma
456
458
 
457
459
  if read.wait_readable([0, @next_check - Time.now].max)
458
460
  req = read.read_nonblock(1)
461
+ next unless req
459
462
 
460
- @next_check = Time.now if req == "!"
461
- next if !req || req == "!"
463
+ if req == PIPE_WAKEUP
464
+ @next_check = Time.now
465
+ next
466
+ end
462
467
 
463
468
  result = read.gets
464
469
  pid = result.to_i
465
470
 
466
- if req == "b" || req == "f"
471
+ if req == PIPE_BOOT || req == PIPE_FORK
467
472
  pid, idx = result.split(':').map(&:to_i)
468
473
  w = worker_at idx
469
474
  w.pid = pid if w.pid.nil?
@@ -471,22 +476,22 @@ module Puma
471
476
 
472
477
  if w = @workers.find { |x| x.pid == pid }
473
478
  case req
474
- when "b"
479
+ when PIPE_BOOT
475
480
  w.boot!
476
481
  log "- Worker #{w.index} (PID: #{pid}) booted in #{w.uptime.round(2)}s, phase: #{w.phase}"
477
482
  @next_check = Time.now
478
483
  workers_not_booted -= 1
479
- when "e"
484
+ when PIPE_EXTERNAL_TERM
480
485
  # external term, see worker method, Signal.trap "SIGTERM"
481
486
  w.term!
482
- when "t"
487
+ when PIPE_TERM
483
488
  w.term unless w.term?
484
- when "p"
489
+ when PIPE_PING
485
490
  status = result.sub(/^\d+/,'').chomp
486
491
  w.ping!(status)
487
492
  @events.fire(:ping!, w)
488
493
 
489
- if in_phased_restart && workers_not_booted.positive? && w0 = worker_at(0)
494
+ if in_phased_restart && @options[:fork_worker] && workers_not_booted.positive? && w0 = worker_at(0)
490
495
  w0.ping!(status)
491
496
  @events.fire(:ping!, w0)
492
497
  end
@@ -496,11 +501,11 @@ module Puma
496
501
  debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
497
502
  booted = true
498
503
  end
499
- when "i"
500
- if @idle_workers[pid]
501
- @idle_workers.delete pid
504
+ when PIPE_IDLE
505
+ if idle_workers[pid]
506
+ idle_workers.delete pid
502
507
  else
503
- @idle_workers[pid] = true
508
+ idle_workers[pid] = true
504
509
  end
505
510
  end
506
511
  else
@@ -560,9 +565,12 @@ module Puma
560
565
  @workers.reject! do |w|
561
566
  next false if w.pid.nil?
562
567
  begin
563
- # When `fork_worker` is enabled, some worker may not be direct children, but grand children.
564
- # Because of this they won't be reaped by `Process.wait2(-1)`, so we need to check them individually)
565
- if reaped_children.delete(w.pid) || (@options[:fork_worker] && Process.wait(w.pid, Process::WNOHANG))
568
+ # We may need to check the PID individually because:
569
+ # 1. From Ruby versions 2.6 to 3.2, `Process.detach` can prevent or delay
570
+ # `Process.wait2(-1)` from detecting a terminated process: https://bugs.ruby-lang.org/issues/19837.
571
+ # 2. When `fork_worker` is enabled, some worker may not be direct children,
572
+ # but grand children. Because of this they won't be reaped by `Process.wait2(-1)`.
573
+ if reaped_children.delete(w.pid) || Process.wait(w.pid, Process::WNOHANG)
566
574
  true
567
575
  else
568
576
  w.term if w.term?
@@ -602,7 +610,11 @@ module Puma
602
610
  end
603
611
 
604
612
  def idle_timed_out_worker_pids
605
- @idle_workers.keys
613
+ idle_workers.keys
614
+ end
615
+
616
+ def idle_workers
617
+ @idle_workers ||= {}
606
618
  end
607
619
  end
608
620
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'rack/builder'
4
3
  require_relative 'plugin'
5
4
  require_relative 'const'
6
5
  require_relative 'dsl'
@@ -131,6 +130,7 @@ module Puma
131
130
  binds: ['tcp://0.0.0.0:9292'.freeze],
132
131
  clean_thread_locals: false,
133
132
  debug: false,
133
+ enable_keep_alives: true,
134
134
  early_hints: nil,
135
135
  environment: 'development'.freeze,
136
136
  # Number of seconds to wait until we get the first data for the request.
@@ -173,8 +173,8 @@ module Puma
173
173
  http_content_length_limit: nil
174
174
  }
175
175
 
176
- def initialize(user_options={}, default_options = {}, &block)
177
- default_options = self.puma_default_options.merge(default_options)
176
+ def initialize(user_options={}, default_options = {}, env = ENV, &block)
177
+ default_options = self.puma_default_options(env).merge(default_options)
178
178
 
179
179
  @options = UserFileDefaultOptions.new(user_options, default_options)
180
180
  @plugins = PluginLoader.new
@@ -186,6 +186,8 @@ module Puma
186
186
  default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
187
187
  end
188
188
 
189
+ @puma_bundler_pruned = env.key? 'PUMA_BUNDLER_PRUNED'
190
+
189
191
  if block
190
192
  configure(&block)
191
193
  end
@@ -216,22 +218,27 @@ module Puma
216
218
  self
217
219
  end
218
220
 
219
- def puma_default_options
221
+ def puma_default_options(env = ENV)
220
222
  defaults = DEFAULTS.dup
221
- puma_options_from_env.each { |k,v| defaults[k] = v if v }
223
+ puma_options_from_env(env).each { |k,v| defaults[k] = v if v }
222
224
  defaults
223
225
  end
224
226
 
225
- def puma_options_from_env
226
- min = ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS']
227
- max = ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS']
228
- workers = ENV['WEB_CONCURRENCY']
227
+ def puma_options_from_env(env = ENV)
228
+ min = env['PUMA_MIN_THREADS'] || env['MIN_THREADS']
229
+ max = env['PUMA_MAX_THREADS'] || env['MAX_THREADS']
230
+ workers = if env['WEB_CONCURRENCY'] == 'auto'
231
+ require_processor_counter
232
+ ::Concurrent.available_processor_count
233
+ else
234
+ env['WEB_CONCURRENCY']
235
+ end
229
236
 
230
237
  {
231
- min_threads: min && Integer(min),
232
- max_threads: max && Integer(max),
233
- workers: workers && Integer(workers),
234
- environment: ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'],
238
+ min_threads: min && min != "" && Integer(min),
239
+ max_threads: max && max != "" && Integer(max),
240
+ workers: workers && workers != "" && Integer(workers),
241
+ environment: env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV'],
235
242
  }
236
243
  end
237
244
 
@@ -311,6 +318,8 @@ module Puma
311
318
  # @param arg [Launcher, Int] `:on_restart` passes Launcher
312
319
  #
313
320
  def run_hooks(key, arg, log_writer, hook_data = nil)
321
+ log_writer.debug "Running #{key} hooks"
322
+
314
323
  @options.all_of(key).each do |b|
315
324
  begin
316
325
  if Array === b
@@ -339,12 +348,22 @@ module Puma
339
348
 
340
349
  private
341
350
 
351
+ def require_processor_counter
352
+ require 'concurrent/utility/processor_counter'
353
+ rescue LoadError
354
+ warn <<~MESSAGE
355
+ WEB_CONCURRENCY=auto requires the "concurrent-ruby" gem to be installed.
356
+ Please add "concurrent-ruby" to your Gemfile.
357
+ MESSAGE
358
+ raise
359
+ end
360
+
342
361
  # Load and use the normal Rack builder if we can, otherwise
343
362
  # fallback to our minimal version.
344
363
  def rack_builder
345
364
  # Load bundler now if we can so that we can pickup rack from
346
365
  # a Gemfile
347
- if ENV.key? 'PUMA_BUNDLER_PRUNED'
366
+ if @puma_bundler_pruned
348
367
  begin
349
368
  require 'bundler/setup'
350
369
  rescue LoadError
@@ -354,11 +373,10 @@ module Puma
354
373
  begin
355
374
  require 'rack'
356
375
  require 'rack/builder'
376
+ ::Rack::Builder
357
377
  rescue LoadError
358
- # ok, use builtin version
359
- return Puma::Rack::Builder
360
- else
361
- return ::Rack::Builder
378
+ require_relative 'rack/builder'
379
+ Puma::Rack::Builder
362
380
  end
363
381
  end
364
382
 
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 = "6.4.3"
104
- CODE_NAME = "The Eagle of Durango"
103
+ PUMA_VERSION = VERSION = "6.6.0"
104
+ CODE_NAME = "Return to Forever"
105
105
 
106
106
  PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
107
107
 
@@ -137,7 +137,7 @@ module Puma
137
137
  }.freeze
138
138
 
139
139
  # The basic max request size we'll try to read.
140
- CHUNK_SIZE = 16 * 1024
140
+ CHUNK_SIZE = 64 * 1024
141
141
 
142
142
  # This is the maximum header that is allowed before a client is booted. The parser detects
143
143
  # this, but we'd also like to do this as well.
@@ -293,5 +293,16 @@ module Puma
293
293
  BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
294
294
 
295
295
  PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
296
+
297
+ # All constants are prefixed with `PIPE_` to avoid name collisions.
298
+ module PipeRequest
299
+ PIPE_WAKEUP = "!"
300
+ PIPE_BOOT = "b"
301
+ PIPE_FORK = "f"
302
+ PIPE_EXTERNAL_TERM = "e"
303
+ PIPE_TERM = "t"
304
+ PIPE_PING = "p"
305
+ PIPE_IDLE = "i"
306
+ end
296
307
  end
297
308
  end