I'm trying to understand (and accomplish) "interweaving" concurrency using ruby Fibers. To do that - I tried to implement my own scheduler but I hit a roadblock.
Here's the code to test:
f1 = Fiber.new do puts "1: start" sleep 2 puts "1: almost" sleep 2 puts "1: done"end.resumef2 = Fiber.new do puts "2: start" sleep 2 puts "2: almost" sleep 2 puts "2: done"end.resume
The theory is: without scheduler, the fibers are run sequentially, and the whole program takes 8s.If I manage to implement a scheduler that can pass on control to another thread on IO (sleep
in this case), the fibers should wait together, and the whole program should take ~4s.
So, I partially implemented my silly scheduler:
class S def initialize @fibers = [] end def block(blocker, timeout = nil) fiber = Fiber.current puts "block", blocker.inspect, timeout.inspect, fiber.inspect @fibers << fiber end def unblock(blocker, fiber) puts "unblock", blocker.inspect, fiber.inspect @fibers.reject!{ _1 == fiber} # remove the fiber fiber.resume end def fiber(&block) Fiber.new(blocking: false, &block).tap(&:resume) end def kernel_sleep(duration = nil) @fibers << Fiber.current end def io_wait(io, events, timeout) puts "io_wait not implemented" endend
I added this part before my test fibers:
Fiber.set_scheduler(S.new) if ENV['SCHEDULER'] == 'yes'
And the results completely surprised me: The fibers are not sleeping at all!
time SCHEDULER=yes ruby fiber-non-blocking-io.rb1: start1: almost1: done2: start2: almost2: doneSCHEDULER=yes ruby fiber-non-blocking-io.rb 0.05s user 0.03s system 99% cpu 0.083 total
What is going on here? I clearly don't understand how the scheduler is supposed to work.
My mental model was something like this (let's focus on kernel_sleep/sleep for simplicity):
- My fiber is resumed, outputs the first message, and hits sleep
- scheduler's
kernel_sleep
is called, and I store it for later2a. (I didn't get to implementingFiber.yield
to pass back the control) - At some point, I should iterate over my sleeping fibers and check if it's time to resume them, but I didn't get to that part.
(The fact that the puts
in the scheduler's code is not showing up also does not help. Clearly, the scheduler does something because the behavior changed, but I can't see those outputs)