Skip to content

Commit 5165a83

Browse files
Fix re-entrant calls to Group#wait_for.
1 parent 1d06f3f commit 5165a83

2 files changed

Lines changed: 32 additions & 1 deletion

File tree

lib/async/container/group.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,10 @@ def wait_for_children(duration = nil)
239239
# Maybe consider using a proper event loop here:
240240
if ready = self.select(duration)
241241
ready.each do |io|
242-
@running[io].resume
242+
if fiber = @running[io]
243+
# This method can be re-entered. While resuming a fiber, a policy hook may be invoked, which may invoke operations on the container. In that case, select may be called again on the same set of waiting fibers. On returning, those fibers may have already completed and removed themselves from @running, so we need to check for that.
244+
fiber.resume
245+
end
243246
end
244247
end
245248
end

test/async/container/group.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,4 +272,32 @@
272272
group.health_check!
273273
end.not.to raise_exception
274274
end
275+
276+
it "handles nil fiber in @running during iteration (re-entrance scenario)" do
277+
# This test simulates a scenario where:
278+
# 1. IO.select returns [io1, io2]
279+
# 2. While resuming fiber for io1, a re-entrant call completes fiber for io2
280+
# 3. When iteration continues to io2, @running[io2] is nil
281+
# Without defensive check (&.), this would crash with NoMethodError
282+
283+
channel1 = Async::Container::Channel.new
284+
channel2 = Async::Container::Channel.new
285+
286+
fiber1 = Fiber.new{group.running.delete(channel2.in)}
287+
fiber2 = Fiber.new{Fiber.yield}
288+
289+
fiber2.resume
290+
291+
group.running[channel1.in] = fiber1
292+
group.running[channel2.in] = fiber2
293+
294+
# Mock select to return both channels:
295+
expect(group).to receive(:select).and_return([channel1.in, channel2.in])
296+
297+
# This should not crash due to &. operator:
298+
group.sleep(0)
299+
300+
# Verify fiber2 was removed
301+
expect(group.running.key?(channel2.in)).to be == false
302+
end
275303
end

0 commit comments

Comments
 (0)