puma 6.4.3-java → 6.6.0-java

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.
@@ -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