Skip to content

Commit

Permalink
Fix Select selector backend per-IO waiter queue handling
Browse files Browse the repository at this point in the history
When multiple fibers wait on different events for the same IO object and
only a subset of the polled events happen, some of the fibers could be
woken up prematurely (with a subset of the events they asked for) and
dropped from the queue.

Fix by re-inserting the waiters into the IO object's queue when none of
their events were triggered.
  • Loading branch information
Math2 committed Jul 24, 2023
1 parent a88be68 commit bd3a9db
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 7 deletions.
26 changes: 19 additions & 7 deletions lib/io/event/selector/select.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,23 @@ def alive?
self.fiber&.alive?
end

def transfer(events)
def dispatch(events, &reactivate)
tail = self.tail
if fiber = self.fiber
self.fiber = nil

fiber.transfer(events & self.events) if fiber.alive?
if fiber.alive?
revents = events & self.events
if revents.zero?
reactivate.call(self)
else
self.fiber = nil
fiber.transfer(revents)
end
else
self.fiber = nil
end
end
self.tail&.transfer(events)

tail&.dispatch(events, &reactivate)
end

def invalidate
Expand Down Expand Up @@ -349,7 +358,10 @@ def select(duration = nil)
end

ready.each do |io, events|
@waiting.delete(io).transfer(events)
@waiting.delete(io).dispatch(events) do |waiter|
waiter.tail = @waiting[io]
@waiting[io] = waiter
end
end

return ready.size
Expand Down
42 changes: 42 additions & 0 deletions test/io/event/selector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,48 @@ def transfer
expect(writable).to be == true
end

it "can read and write from two different fibers (alternate)" do
read_fiber = Fiber.new do
events << :wait_readable

expect(
selector.io_wait(Fiber.current, local, IO::READABLE)
).to be == IO::READABLE

events << :readable
end

write_fiber = Fiber.new do
events << :wait_writable

expect(
selector.io_wait(Fiber.current, local, IO::WRITABLE)
).to be == IO::WRITABLE

events << :writable
end

events << :transfer
read_fiber.transfer
write_fiber.transfer

events << :select1
selector.select(1)
remote.puts "Hello World"
events << :select2
selector.select(1)

expect(events).to be == [
:transfer,
:wait_readable,
:wait_writable,
:select1,
:writable,
:select2,
:readable,
]
end

it "can handle exception during wait" do
fiber = Fiber.new do
events << :wait_readable
Expand Down

0 comments on commit bd3a9db

Please sign in to comment.