2020-05-14 06:10:55 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2020-09-16 05:36:39 -04:00
|
|
|
# This is an example and simplified scheduler for test purposes.
|
|
|
|
# It is not efficient for a large number of file descriptors as it uses IO.select().
|
|
|
|
# Production Fiber schedulers should use epoll/kqueue/etc.
|
|
|
|
|
2020-05-14 06:10:55 -04:00
|
|
|
require 'fiber'
|
2020-07-15 23:19:47 -04:00
|
|
|
require 'socket'
|
2020-05-14 06:10:55 -04:00
|
|
|
|
|
|
|
begin
|
|
|
|
require 'io/nonblock'
|
|
|
|
rescue LoadError
|
|
|
|
# Ignore.
|
|
|
|
end
|
|
|
|
|
|
|
|
class Scheduler
|
|
|
|
def initialize
|
|
|
|
@readable = {}
|
|
|
|
@writable = {}
|
|
|
|
@waiting = {}
|
2020-09-05 00:26:24 -04:00
|
|
|
|
2020-09-19 19:34:02 -04:00
|
|
|
@closed = false
|
|
|
|
|
2020-09-05 00:26:24 -04:00
|
|
|
@lock = Mutex.new
|
|
|
|
@locking = 0
|
2020-09-17 06:55:17 -04:00
|
|
|
@ready = []
|
2020-05-14 06:10:55 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
attr :readable
|
|
|
|
attr :writable
|
|
|
|
attr :waiting
|
|
|
|
|
|
|
|
def next_timeout
|
2020-05-14 12:21:12 -04:00
|
|
|
_fiber, timeout = @waiting.min_by{|key, value| value}
|
2020-05-14 06:10:55 -04:00
|
|
|
|
|
|
|
if timeout
|
|
|
|
offset = timeout - current_time
|
|
|
|
|
|
|
|
if offset < 0
|
|
|
|
return 0
|
|
|
|
else
|
|
|
|
return offset
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def run
|
2020-09-05 00:26:24 -04:00
|
|
|
@urgent = IO.pipe
|
|
|
|
|
|
|
|
while @readable.any? or @writable.any? or @waiting.any? or @locking.positive?
|
2020-05-14 06:10:55 -04:00
|
|
|
# Can only handle file descriptors up to 1024...
|
2020-09-05 00:26:24 -04:00
|
|
|
readable, writable = IO.select(@readable.keys + [@urgent.first], @writable.keys, [], next_timeout)
|
2020-05-14 06:10:55 -04:00
|
|
|
|
|
|
|
# puts "readable: #{readable}" if readable&.any?
|
|
|
|
# puts "writable: #{writable}" if writable&.any?
|
|
|
|
|
|
|
|
readable&.each do |io|
|
2020-09-05 22:48:52 -04:00
|
|
|
if fiber = @readable.delete(io)
|
|
|
|
fiber.resume
|
|
|
|
elsif io == @urgent.first
|
|
|
|
@urgent.first.read_nonblock(1024)
|
|
|
|
end
|
2020-05-14 06:10:55 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
writable&.each do |io|
|
2020-09-05 22:48:52 -04:00
|
|
|
if fiber = @writable.delete(io)
|
|
|
|
fiber.resume
|
|
|
|
end
|
2020-05-14 06:10:55 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
if @waiting.any?
|
|
|
|
time = current_time
|
2020-09-17 06:55:17 -04:00
|
|
|
waiting, @waiting = @waiting, {}
|
2020-05-14 06:10:55 -04:00
|
|
|
|
|
|
|
waiting.each do |fiber, timeout|
|
|
|
|
if timeout <= time
|
|
|
|
fiber.resume
|
|
|
|
else
|
|
|
|
@waiting[fiber] = timeout
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2020-09-05 00:26:24 -04:00
|
|
|
|
|
|
|
if @ready.any?
|
|
|
|
ready = nil
|
|
|
|
|
|
|
|
@lock.synchronize do
|
2020-09-17 06:55:17 -04:00
|
|
|
ready, @ready = @ready, []
|
2020-09-05 00:26:24 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
ready.each do |fiber|
|
|
|
|
fiber.resume
|
|
|
|
end
|
|
|
|
end
|
2020-05-14 06:10:55 -04:00
|
|
|
end
|
2020-09-05 00:26:24 -04:00
|
|
|
ensure
|
|
|
|
@urgent.each(&:close)
|
2020-09-17 08:45:44 -04:00
|
|
|
@urgent = nil
|
2020-05-14 06:10:55 -04:00
|
|
|
end
|
|
|
|
|
2020-09-19 19:34:02 -04:00
|
|
|
def close
|
|
|
|
self.run
|
|
|
|
ensure
|
|
|
|
@closed = true
|
|
|
|
|
|
|
|
# We freeze to detect any inadvertant modifications after the scheduler is closed:
|
|
|
|
self.freeze
|
|
|
|
end
|
|
|
|
|
|
|
|
def closed?
|
|
|
|
@closed
|
|
|
|
end
|
|
|
|
|
2020-05-14 06:10:55 -04:00
|
|
|
def current_time
|
|
|
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
|
|
end
|
|
|
|
|
2020-08-19 21:51:45 -04:00
|
|
|
def io_wait(io, events, duration)
|
2020-08-19 21:49:09 -04:00
|
|
|
unless (events & IO::READABLE).zero?
|
2020-05-14 06:10:55 -04:00
|
|
|
@readable[io] = Fiber.current
|
|
|
|
end
|
|
|
|
|
2020-08-19 21:49:09 -04:00
|
|
|
unless (events & IO::WRITABLE).zero?
|
2020-05-14 06:10:55 -04:00
|
|
|
@writable[io] = Fiber.current
|
|
|
|
end
|
|
|
|
|
|
|
|
Fiber.yield
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2020-09-17 08:52:04 -04:00
|
|
|
# Used for Kernel#sleep and Mutex#sleep
|
2020-09-17 08:30:40 -04:00
|
|
|
def kernel_sleep(duration = nil)
|
|
|
|
# p [__method__, duration]
|
|
|
|
if duration
|
|
|
|
@waiting[Fiber.current] = current_time + duration
|
|
|
|
end
|
|
|
|
|
|
|
|
Fiber.yield
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2020-09-17 08:52:04 -04:00
|
|
|
# Used when blocking on synchronization (Mutex#lock, Queue#pop, SizedQueue#push, ...)
|
2020-09-17 08:30:40 -04:00
|
|
|
def block(blocker)
|
|
|
|
# p [__method__, blocker]
|
2020-09-05 00:26:24 -04:00
|
|
|
@locking += 1
|
|
|
|
Fiber.yield
|
|
|
|
ensure
|
|
|
|
@locking -= 1
|
|
|
|
end
|
|
|
|
|
2020-09-17 11:26:52 -04:00
|
|
|
# Used when synchronization wakes up a previously-blocked fiber (Mutex#unlock, Queue#push, ...).
|
|
|
|
# This might be called from another thread.
|
2020-09-17 08:30:40 -04:00
|
|
|
def unblock(blocker, fiber)
|
|
|
|
# p [__method__, blocker, fiber]
|
2020-09-05 00:26:24 -04:00
|
|
|
@lock.synchronize do
|
|
|
|
@ready << fiber
|
2020-09-05 22:48:52 -04:00
|
|
|
end
|
2020-09-05 00:26:24 -04:00
|
|
|
|
2020-09-05 22:48:52 -04:00
|
|
|
if io = @urgent&.last
|
2020-09-17 08:45:44 -04:00
|
|
|
io.write_nonblock('.')
|
2020-09-05 00:26:24 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-05-14 06:10:55 -04:00
|
|
|
def fiber(&block)
|
|
|
|
fiber = Fiber.new(blocking: false, &block)
|
|
|
|
|
|
|
|
fiber.resume
|
|
|
|
|
|
|
|
return fiber
|
|
|
|
end
|
|
|
|
end
|