2018-09-17 16:41:14 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2022-09-21 06:00:30 +00:00
|
|
|
require_relative 'queue_close' unless ::Queue.instance_methods.include? :close
|
2019-02-20 00:38:21 +00:00
|
|
|
|
2012-07-23 17:26:52 +00:00
|
|
|
module Puma
|
2021-01-12 16:39:33 +00:00
|
|
|
class UnsupportedBackend < StandardError; end
|
|
|
|
|
2020-10-06 13:22:53 +00:00
|
|
|
# Monitors a collection of IO objects, calling a block whenever
|
|
|
|
# any monitored object either receives data or times out, or when the Reactor shuts down.
|
2018-04-30 22:00:56 +00:00
|
|
|
#
|
2020-10-06 13:22:53 +00:00
|
|
|
# The waiting/wake up is performed with nio4r, which will use the appropriate backend (libev,
|
|
|
|
# Java NIO or just plain IO#select). The call to `NIO::Selector#select` will
|
|
|
|
# 'wakeup' any IO object that receives data.
|
2018-05-01 16:19:50 +00:00
|
|
|
#
|
2020-10-06 13:22:53 +00:00
|
|
|
# This class additionally tracks a timeout for every added object,
|
|
|
|
# and wakes up any object when its timeout elapses.
|
2018-05-01 16:19:50 +00:00
|
|
|
#
|
2020-10-06 13:22:53 +00:00
|
|
|
# The implementation uses a Queue to synchronize adding new objects from the internal select loop.
|
2012-07-23 17:26:52 +00:00
|
|
|
class Reactor
|
2020-10-06 13:22:53 +00:00
|
|
|
# Create a new Reactor to monitor IO objects added by #add.
|
|
|
|
# The provided block will be invoked when an IO has data available to read,
|
|
|
|
# its timeout elapses, or when the Reactor shuts down.
|
2021-01-12 16:39:33 +00:00
|
|
|
def initialize(backend, &block)
|
2020-10-20 13:31:23 +00:00
|
|
|
require 'nio'
|
2021-01-12 16:39:33 +00:00
|
|
|
unless backend == :auto || NIO::Selector.backends.include?(backend)
|
|
|
|
raise "unsupported IO selector backend: #{backend} (available backends: #{NIO::Selector.backends.join(', ')})"
|
|
|
|
end
|
|
|
|
@selector = backend == :auto ? NIO::Selector.new : NIO::Selector.new(backend)
|
2020-10-06 13:22:53 +00:00
|
|
|
@input = Queue.new
|
2012-07-23 17:26:52 +00:00
|
|
|
@timeouts = []
|
2020-10-06 13:22:53 +00:00
|
|
|
@block = block
|
2012-07-23 17:26:52 +00:00
|
|
|
end
|
|
|
|
|
2020-10-06 13:22:53 +00:00
|
|
|
# Run the internal select loop, using a background thread by default.
|
|
|
|
def run(background=true)
|
|
|
|
if background
|
|
|
|
@thread = Thread.new do
|
|
|
|
Puma.set_thread_name "reactor"
|
|
|
|
select_loop
|
2012-07-23 17:26:52 +00:00
|
|
|
end
|
2020-10-06 13:22:53 +00:00
|
|
|
else
|
|
|
|
select_loop
|
2012-07-23 17:26:52 +00:00
|
|
|
end
|
2013-10-28 12:56:45 +00:00
|
|
|
end
|
|
|
|
|
2020-10-27 13:38:34 +00:00
|
|
|
# Add a new client to monitor.
|
2020-10-06 13:22:53 +00:00
|
|
|
# The object must respond to #timeout and #timeout_at.
|
|
|
|
# Returns false if the reactor is already shut down.
|
2020-10-27 13:38:34 +00:00
|
|
|
def add(client)
|
|
|
|
@input << client
|
2020-10-06 13:22:53 +00:00
|
|
|
@selector.wakeup
|
|
|
|
true
|
|
|
|
rescue ClosedQueueError
|
|
|
|
false
|
2012-07-23 17:26:52 +00:00
|
|
|
end
|
|
|
|
|
2020-10-06 13:22:53 +00:00
|
|
|
# Shutdown the reactor, blocking until the background thread is finished.
|
|
|
|
def shutdown
|
|
|
|
@input.close
|
|
|
|
begin
|
|
|
|
@selector.wakeup
|
|
|
|
rescue IOError # Ignore if selector is already closed
|
2013-10-28 13:27:30 +00:00
|
|
|
end
|
2022-09-30 06:06:32 +00:00
|
|
|
@thread&.join
|
2012-07-23 17:26:52 +00:00
|
|
|
end
|
|
|
|
|
2020-10-06 13:22:53 +00:00
|
|
|
private
|
2012-07-30 23:12:23 +00:00
|
|
|
|
2020-10-06 13:22:53 +00:00
|
|
|
def select_loop
|
|
|
|
begin
|
|
|
|
until @input.closed? && @input.empty?
|
|
|
|
# Wakeup any registered object that receives incoming data.
|
|
|
|
# Block until the earliest timeout or Selector#wakeup is called.
|
|
|
|
timeout = (earliest = @timeouts.first) && earliest.timeout
|
|
|
|
@selector.select(timeout) {|mon| wakeup!(mon.value)}
|
|
|
|
|
|
|
|
# Wakeup all objects that timed out.
|
|
|
|
timed_out = @timeouts.take_while {|t| t.timeout == 0}
|
2022-09-30 06:06:32 +00:00
|
|
|
timed_out.each { |c| wakeup! c }
|
2020-10-06 13:22:53 +00:00
|
|
|
|
|
|
|
unless @input.empty?
|
2020-10-25 19:58:49 +00:00
|
|
|
until @input.empty?
|
|
|
|
client = @input.pop
|
|
|
|
register(client) if client.io_ok?
|
|
|
|
end
|
2020-10-06 13:22:53 +00:00
|
|
|
@timeouts.sort_by!(&:timeout_at)
|
|
|
|
end
|
2012-07-30 23:12:23 +00:00
|
|
|
end
|
2020-10-06 13:22:53 +00:00
|
|
|
rescue StandardError => e
|
|
|
|
STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
|
|
|
|
STDERR.puts e.backtrace
|
|
|
|
retry
|
2012-07-30 23:12:23 +00:00
|
|
|
end
|
2020-10-06 13:22:53 +00:00
|
|
|
# Wakeup all remaining objects on shutdown.
|
2020-10-08 01:51:59 +00:00
|
|
|
@timeouts.each(&@block)
|
2020-10-06 13:22:53 +00:00
|
|
|
@selector.close
|
2012-07-30 23:12:23 +00:00
|
|
|
end
|
|
|
|
|
2020-10-06 13:22:53 +00:00
|
|
|
# Start monitoring the object.
|
2020-10-27 13:38:34 +00:00
|
|
|
def register(client)
|
|
|
|
@selector.register(client.to_io, :r).value = client
|
|
|
|
@timeouts << client
|
2020-11-16 20:31:07 +00:00
|
|
|
rescue ArgumentError
|
|
|
|
# unreadable clients raise error when processed by NIO
|
2012-07-23 17:26:52 +00:00
|
|
|
end
|
2012-08-10 17:10:30 +00:00
|
|
|
|
2020-10-06 13:22:53 +00:00
|
|
|
# 'Wake up' a monitored object by calling the provided block.
|
|
|
|
# Stop monitoring the object if the block returns `true`.
|
2020-10-27 13:38:34 +00:00
|
|
|
def wakeup!(client)
|
|
|
|
if @block.call client
|
|
|
|
@selector.deregister client.to_io
|
|
|
|
@timeouts.delete client
|
2013-02-05 06:31:40 +00:00
|
|
|
end
|
2012-09-03 03:33:09 +00:00
|
|
|
end
|
2012-07-23 17:26:52 +00:00
|
|
|
end
|
|
|
|
end
|