puma 6.6.1-java → 7.0.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.
data/lib/puma/reactor.rb CHANGED
@@ -15,6 +15,12 @@ module Puma
15
15
  #
16
16
  # The implementation uses a Queue to synchronize adding new objects from the internal select loop.
17
17
  class Reactor
18
+
19
+ # @!attribute [rw] reactor_max
20
+ # Maximum number of clients in the selector. Reset with calls to `Server.stats`.
21
+ attr_accessor :reactor_max
22
+ attr_reader :reactor_size
23
+
18
24
  # Create a new Reactor to monitor IO objects added by #add.
19
25
  # The provided block will be invoked when an IO has data available to read,
20
26
  # its timeout elapses, or when the Reactor shuts down.
@@ -29,6 +35,8 @@ module Puma
29
35
  @input = Queue.new
30
36
  @timeouts = []
31
37
  @block = block
38
+ @reactor_size = 0
39
+ @reactor_max = 0
32
40
  end
33
41
 
34
42
  # Run the internal select loop, using a background thread by default.
@@ -73,11 +81,15 @@ module Puma
73
81
  # Wakeup any registered object that receives incoming data.
74
82
  # Block until the earliest timeout or Selector#wakeup is called.
75
83
  timeout = (earliest = @timeouts.first) && earliest.timeout
76
- @selector.select(timeout) {|mon| wakeup!(mon.value)}
84
+ monitor_wake_up = false
85
+ @selector.select(timeout) do |monitor|
86
+ monitor_wake_up = true
87
+ wakeup!(monitor.value)
88
+ end
77
89
 
78
90
  # Wakeup all objects that timed out.
79
- timed_out = @timeouts.take_while {|t| t.timeout == 0}
80
- timed_out.each { |c| wakeup! c }
91
+ timed_out = @timeouts.take_while { |client| client.timeout == 0 }
92
+ timed_out.each { |client| wakeup!(client) }
81
93
 
82
94
  unless @input.empty?
83
95
  until @input.empty?
@@ -94,7 +106,7 @@ module Puma
94
106
  # NoMethodError may be rarely raised when calling @selector.select, which
95
107
  # is odd. Regardless, it may continue for thousands of calls if retried.
96
108
  # Also, when it raises, @selector.close also raises an error.
97
- if NoMethodError === e
109
+ if !monitor_wake_up && NoMethodError === e
98
110
  close_selector = false
99
111
  else
100
112
  retry
@@ -108,6 +120,8 @@ module Puma
108
120
  # Start monitoring the object.
109
121
  def register(client)
110
122
  @selector.register(client.to_io, :r).value = client
123
+ @reactor_size += 1
124
+ @reactor_max = @reactor_size if @reactor_max < @reactor_size
111
125
  @timeouts << client
112
126
  rescue ArgumentError
113
127
  # unreadable clients raise error when processed by NIO
@@ -118,6 +132,7 @@ module Puma
118
132
  def wakeup!(client)
119
133
  if @block.call client
120
134
  @selector.deregister client.to_io
135
+ @reactor_size -= 1
121
136
  @timeouts.delete client
122
137
  end
123
138
  end
data/lib/puma/request.rb CHANGED
@@ -52,6 +52,7 @@ module Puma
52
52
  io_buffer = client.io_buffer
53
53
  socket = client.io # io may be a MiniSSL::Socket
54
54
  app_body = nil
55
+ error = nil
55
56
 
56
57
  return false if closed_socket?(socket)
57
58
 
@@ -68,7 +69,7 @@ module Puma
68
69
  end
69
70
 
70
71
  env[HIJACK_P] = true
71
- env[HIJACK] = client
72
+ env[HIJACK] = client.method :full_hijack
72
73
 
73
74
  env[RACK_INPUT] = client.body
74
75
  env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
@@ -92,6 +93,7 @@ module Puma
92
93
  # array, we will invoke them when the request is done.
93
94
  #
94
95
  env[RACK_AFTER_REPLY] ||= []
96
+ env[RACK_RESPONSE_FINISHED] ||= []
95
97
 
96
98
  begin
97
99
  if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
@@ -119,15 +121,15 @@ module Puma
119
121
 
120
122
  return :async
121
123
  end
122
- rescue ThreadPool::ForceShutdown => e
123
- @log_writer.unknown_error e, client, "Rack app"
124
+ rescue ThreadPool::ForceShutdown => error
125
+ @log_writer.unknown_error error, client, "Rack app"
124
126
  @log_writer.log "Detected force shutdown of a thread"
125
127
 
126
- status, headers, res_body = lowlevel_error(e, env, 503)
127
- rescue Exception => e
128
- @log_writer.unknown_error e, client, "Rack app"
128
+ status, headers, res_body = lowlevel_error(error, env, 503)
129
+ rescue Exception => error
130
+ @log_writer.unknown_error error, client, "Rack app"
129
131
 
130
- status, headers, res_body = lowlevel_error(e, env, 500)
132
+ status, headers, res_body = lowlevel_error(error, env, 500)
131
133
  end
132
134
  prepare_response(status, headers, res_body, requests, client)
133
135
  ensure
@@ -144,6 +146,16 @@ module Puma
144
146
  end
145
147
  end
146
148
  end
149
+
150
+ if response_finished = env[RACK_RESPONSE_FINISHED]
151
+ response_finished.reverse_each do |o|
152
+ begin
153
+ o.call(env, status, headers, error)
154
+ rescue StandardError => e
155
+ @log_writer.debug_error e
156
+ end
157
+ end
158
+ end
147
159
  end
148
160
 
149
161
  # Assembles the headers and prepares the body for actually sending the
@@ -164,17 +176,7 @@ module Puma
164
176
  return false if closed_socket?(socket)
165
177
 
166
178
  # Close the connection after a reasonable number of inline requests
167
- # if the server is at capacity and the listener has a new connection ready.
168
- # This allows Puma to service connections fairly when the number
169
- # of concurrent connections exceeds the size of the threadpool.
170
- force_keep_alive = if @enable_keep_alives
171
- requests < @max_fast_inline ||
172
- @thread_pool.busy_threads < @max_threads ||
173
- !client.listener.to_io.wait_readable(0)
174
- else
175
- # Always set force_keep_alive to false if the server has keep-alives not enabled.
176
- false
177
- end
179
+ force_keep_alive = @enable_keep_alives && client.requests_served < @max_keep_alive
178
180
 
179
181
  resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
180
182
 
@@ -267,7 +269,8 @@ module Puma
267
269
 
268
270
  fast_write_response socket, body, io_buffer, chunked, content_length.to_i
269
271
  body.close if close_body
270
- keep_alive
272
+ # if we're shutting down, close keep_alive connections
273
+ !shutting_down? && keep_alive
271
274
  end
272
275
 
273
276
  # @param env [Hash] see Puma::Client#env, from request
@@ -478,7 +481,7 @@ module Puma
478
481
 
479
482
  # The legacy HTTP_VERSION header can be sent as a client header.
480
483
  # Rack v4 may remove using HTTP_VERSION. If so, remove this line.
481
- env[HTTP_VERSION] = env[SERVER_PROTOCOL]
484
+ env[HTTP_VERSION] = env[SERVER_PROTOCOL] if @env_set_http_version
482
485
  end
483
486
  private :normalize_env
484
487
 
@@ -585,7 +588,7 @@ module Puma
585
588
  # response body
586
589
  # @param io_buffer [Puma::IOBuffer] modified inn place
587
590
  # @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
588
- # status and `@max_fast_inline`
591
+ # status and `@max_keep_alive`
589
592
  # @return [Hash] resp_info
590
593
  # @version 5.0.3
591
594
  #
@@ -668,10 +671,10 @@ module Puma
668
671
  if ary
669
672
  ary.each do |v|
670
673
  next if illegal_header_value?(v)
671
- io_buffer.append k, colon, v, line_ending
674
+ io_buffer.append k.downcase, colon, v, line_ending
672
675
  end
673
676
  else
674
- io_buffer.append k, colon, line_ending
677
+ io_buffer.append k.downcase, colon, line_ending
675
678
  end
676
679
  end
677
680
 
data/lib/puma/runner.rb CHANGED
@@ -33,7 +33,6 @@ module Puma
33
33
  @wakeup.write PIPE_WAKEUP unless @wakeup.closed?
34
34
 
35
35
  rescue SystemCallError, IOError
36
- Puma::Util.purge_interrupt_queue
37
36
  end
38
37
 
39
38
  def development?
@@ -93,22 +92,6 @@ module Puma
93
92
  @control.binder.close_listeners if @control
94
93
  end
95
94
 
96
- # @!attribute [r] ruby_engine
97
- # @deprecated Use `RUBY_DESCRIPTION` instead
98
- def ruby_engine
99
- warn "Puma::Runner#ruby_engine is deprecated; use RUBY_DESCRIPTION instead. It will be removed in puma v7."
100
-
101
- if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
102
- "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
103
- else
104
- if defined?(RUBY_ENGINE_VERSION)
105
- "#{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} - ruby #{RUBY_VERSION}"
106
- else
107
- "#{RUBY_ENGINE} #{RUBY_VERSION}"
108
- end
109
- end
110
- end
111
-
112
95
  def output_header(mode)
113
96
  min_t = @options[:min_threads]
114
97
  max_t = @options[:max_threads]
@@ -128,6 +111,14 @@ module Puma
128
111
  end
129
112
  end
130
113
 
114
+ def warn_ruby_mn_threads
115
+ return if !ENV.key?('RUBY_MN_THREADS')
116
+
117
+ log "! WARNING: Detected `RUBY_MN_THREADS=#{ENV['RUBY_MN_THREADS']}`"
118
+ log "! This setting is known to cause performance regressions with Puma."
119
+ log "! Consider disabling this environment variable: https://github.com/puma/puma/issues/3720"
120
+ end
121
+
131
122
  def redirected_io?
132
123
  @options[:redirect_stdout] || @options[:redirect_stderr]
133
124
  end
data/lib/puma/server.rb CHANGED
@@ -12,14 +12,14 @@ require_relative 'client'
12
12
  require_relative 'binder'
13
13
  require_relative 'util'
14
14
  require_relative 'request'
15
+ require_relative 'configuration'
15
16
 
16
17
  require 'socket'
17
18
  require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
18
19
 
19
20
  module Puma
20
-
21
- # This method was private on Ruby 2.4 but became public on Ruby 2.5+:
22
- Thread.send(:attr_accessor, :puma_server)
21
+ # Add `Thread#puma_server` and `Thread#puma_server=`
22
+ Thread.attr_accessor(:puma_server)
23
23
 
24
24
  # The HTTP Server itself. Serves out a single Rack app.
25
25
  #
@@ -32,6 +32,14 @@ module Puma
32
32
  #
33
33
  # Each `Puma::Server` will have one reactor and one thread pool.
34
34
  class Server
35
+ module FiberPerRequest
36
+ def handle_request(client, requests)
37
+ Fiber.new do
38
+ super
39
+ end.resume
40
+ end
41
+ end
42
+
35
43
  include Puma::Const
36
44
  include Request
37
45
 
@@ -77,6 +85,9 @@ module Puma
77
85
 
78
86
  @thread = nil
79
87
  @thread_pool = nil
88
+ @reactor = nil
89
+
90
+ @env_set_http_version = nil
80
91
 
81
92
  @options = if options.is_a?(UserFileDefaultOptions)
82
93
  options
@@ -94,11 +105,16 @@ module Puma
94
105
  @min_threads = @options[:min_threads]
95
106
  @max_threads = @options[:max_threads]
96
107
  @queue_requests = @options[:queue_requests]
97
- @max_fast_inline = @options[:max_fast_inline]
108
+ @max_keep_alive = @options[:max_keep_alive]
98
109
  @enable_keep_alives = @options[:enable_keep_alives]
110
+ @enable_keep_alives &&= @queue_requests
99
111
  @io_selector_backend = @options[:io_selector_backend]
100
112
  @http_content_length_limit = @options[:http_content_length_limit]
101
113
 
114
+ if @options[:fiber_per_request]
115
+ singleton_class.prepend(FiberPerRequest)
116
+ end
117
+
102
118
  # make this a hash, since we prefer `key?` over `include?`
103
119
  @supported_http_methods =
104
120
  if @options[:supported_http_methods] == :any
@@ -114,7 +130,7 @@ module Puma
114
130
  temp = !!(@options[:environment] =~ /\A(development|test)\z/)
115
131
  @leak_stack_on_error = @options[:environment] ? temp : true
116
132
 
117
- @binder = Binder.new(log_writer)
133
+ @binder = Binder.new(log_writer, @options)
118
134
 
119
135
  ENV['RACK_ENV'] ||= "development"
120
136
 
@@ -165,7 +181,6 @@ module Puma
165
181
  begin
166
182
  skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
167
183
  rescue IOError, SystemCallError
168
- Puma::Util.purge_interrupt_queue
169
184
  end
170
185
  end
171
186
 
@@ -174,7 +189,6 @@ module Puma
174
189
  begin
175
190
  skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
176
191
  rescue IOError, SystemCallError
177
- Puma::Util.purge_interrupt_queue
178
192
  end
179
193
  end
180
194
  else
@@ -195,7 +209,6 @@ module Puma
195
209
  begin
196
210
  tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
197
211
  rescue IOError, SystemCallError
198
- Puma::Util.purge_interrupt_queue
199
212
  @precheck_closing = false
200
213
  false
201
214
  else
@@ -220,7 +233,6 @@ module Puma
220
233
  @thread_pool&.spawned
221
234
  end
222
235
 
223
-
224
236
  # This number represents the number of requests that
225
237
  # the server is capable of taking right now.
226
238
  #
@@ -318,12 +330,16 @@ module Puma
318
330
  end
319
331
 
320
332
  def handle_servers
333
+ @env_set_http_version = Object.const_defined?(:Rack) && ::Rack.respond_to?(:release) &&
334
+ Gem::Version.new(::Rack.release) < Gem::Version.new('3.1.0')
335
+
321
336
  begin
322
337
  check = @check
323
338
  sockets = [check] + @binder.ios
324
339
  pool = @thread_pool
325
340
  queue_requests = @queue_requests
326
341
  drain = options[:drain_on_shutdown] ? 0 : nil
342
+ max_flt = @max_threads.to_f
327
343
 
328
344
  addr_send_name, addr_value = case options[:remote_address]
329
345
  when :value
@@ -364,8 +380,20 @@ module Puma
364
380
  if sock == check
365
381
  break if handle_check
366
382
  else
367
- pool.wait_until_not_full
368
- pool.wait_for_less_busy_worker(options[:wait_for_less_busy_worker]) if @clustered
383
+ # if ThreadPool out_of_band code is running, we don't want to add
384
+ # clients until the code is finished.
385
+ sleep 0.001 while pool.out_of_band_running
386
+
387
+ # only use delay when clustered and busy
388
+ if pool.busy_threads >= @max_threads
389
+ if @clustered
390
+ delay = 0.0001 * ((@reactor&.reactor_size || 0) + pool.busy_threads * 1.5)/max_flt
391
+ sleep delay
392
+ else
393
+ # use small sleep for busy single worker
394
+ sleep 0.0001
395
+ end
396
+ end
369
397
 
370
398
  io = begin
371
399
  sock.accept_nonblock
@@ -447,14 +475,12 @@ module Puma
447
475
  # Advertise this server into the thread
448
476
  Thread.current.puma_server = self
449
477
 
450
- clean_thread_locals = options[:clean_thread_locals]
451
478
  close_socket = true
452
479
 
453
480
  requests = 0
454
481
 
455
482
  begin
456
- if @queue_requests &&
457
- !client.eagerly_finish
483
+ if @queue_requests && !client.eagerly_finish
458
484
 
459
485
  client.set_timeout(@first_data_timeout)
460
486
  if @reactor.add client
@@ -467,39 +493,31 @@ module Puma
467
493
  client.finish(@first_data_timeout)
468
494
  end
469
495
 
470
- while true
471
- @requests_count += 1
472
- case handle_request(client, requests + 1)
473
- when false
474
- break
475
- when :async
476
- close_socket = false
477
- break
478
- when true
479
- ThreadPool.clean_thread_locals if clean_thread_locals
480
-
481
- requests += 1
482
-
483
- # As an optimization, try to read the next request from the
484
- # socket for a short time before returning to the reactor.
485
- fast_check = @status == :run
496
+ @requests_count += 1
497
+ case handle_request(client, requests + 1)
498
+ when false
499
+ when :async
500
+ close_socket = false
501
+ when true
502
+ requests += 1
486
503
 
487
- # Always pass the client back to the reactor after a reasonable
488
- # number of inline requests if there are other requests pending.
489
- fast_check = false if requests >= @max_fast_inline &&
490
- @thread_pool.backlog > 0
504
+ client.reset
491
505
 
492
- next_request_ready = with_force_shutdown(client) do
493
- client.reset(fast_check)
494
- end
506
+ # This indicates data exists in the client read buffer and there may be
507
+ # additional requests on it, so process them
508
+ next_request_ready = if client.has_back_to_back_requests?
509
+ with_force_shutdown(client) { client.process_back_to_back_requests }
510
+ else
511
+ nil
512
+ end
495
513
 
496
- unless next_request_ready
497
- break unless @queue_requests
498
- client.set_timeout @persistent_timeout
499
- if @reactor.add client
500
- close_socket = false
501
- break
502
- end
514
+ if next_request_ready
515
+ @thread_pool << client
516
+ close_socket = false
517
+ elsif @queue_requests
518
+ client.set_timeout @persistent_timeout
519
+ if @reactor.add client
520
+ close_socket = false
503
521
  end
504
522
  end
505
523
  end
@@ -514,7 +532,6 @@ module Puma
514
532
  begin
515
533
  client.close if close_socket
516
534
  rescue IOError, SystemCallError
517
- Puma::Util.purge_interrupt_queue
518
535
  # Already closed
519
536
  rescue StandardError => e
520
537
  @log_writer.unknown_error e, nil, "Client"
@@ -615,11 +632,10 @@ module Puma
615
632
  @notify << message
616
633
  rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
617
634
  # The server, in another thread, is shutting down
618
- Puma::Util.purge_interrupt_queue
619
635
  rescue RuntimeError => e
620
636
  # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
621
637
  if e.message.include?('IOError')
622
- Puma::Util.purge_interrupt_queue
638
+ # ignore
623
639
  else
624
640
  raise e
625
641
  end
@@ -650,7 +666,16 @@ module Puma
650
666
 
651
667
  # List of methods invoked by #stats.
652
668
  # @version 5.0.0
653
- STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count, :busy_threads].freeze
669
+ STAT_METHODS = [
670
+ :backlog,
671
+ :running,
672
+ :pool_capacity,
673
+ :busy_threads,
674
+ :backlog_max,
675
+ :max_threads,
676
+ :requests_count,
677
+ :reactor_max,
678
+ ].freeze
654
679
 
655
680
  # Returns a hash of stats about the running server for reporting purposes.
656
681
  # @version 5.0.0
@@ -660,9 +685,16 @@ module Puma
660
685
  stats = @thread_pool&.stats || {}
661
686
  stats[:max_threads] = @max_threads
662
687
  stats[:requests_count] = @requests_count
688
+ stats[:reactor_max] = @reactor.reactor_max
689
+ reset_max
663
690
  stats
664
691
  end
665
692
 
693
+ def reset_max
694
+ @reactor.reactor_max = 0
695
+ @thread_pool.reset_max
696
+ end
697
+
666
698
  # below are 'delegations' to binder
667
699
  # remove in Puma 7?
668
700
 
data/lib/puma/single.rb CHANGED
@@ -53,9 +53,12 @@ module Puma
53
53
  server_thread = server.run
54
54
 
55
55
  log "Use Ctrl-C to stop"
56
+
57
+ warn_ruby_mn_threads
58
+
56
59
  redirect_io
57
60
 
58
- @events.fire_on_booted!
61
+ @events.fire_after_booted!
59
62
 
60
63
  debug_loaded_extensions("Loaded Extensions:") if @log_writer.debug?
61
64