Fiber scheduler
Ruby's missing Fiber Scheduler implementation.
Ruby 3 has [Fiber Scheduler hooks](https://docs.ruby-lang.org/en/3.1/Fiber/SchedulerInterface.html) that enable asynchronous programming. In order to make this work you need a Fiber Scheduler implementation, **but Ruby does not provide a default one**. The project is written primarily in Ruby, distributed under the MIT License license, first published in 2022. Key topics include: async, async-programming, asynchronous-programming, concurrency, fiber.
Fiber Scheduler
Ruby 3 has
Fiber Scheduler hooks
that enable asynchronous programming. In order to make this work you need a
Fiber Scheduler implementation, but Ruby does not provide a default one.
This gem aims to fill that void by providing a Fiber Scheduler class that makes
a great default. It's easy to use, performant, and can be used with
just built-in Ruby methods.
fiber_scheduler's killer feature 💣 is full compatibility with any other
Fiber Scheduler implementation, including the
async gem. Write code using
fiber_scheduler and it works seamlessly with async, bsync or whatever
other _sync gem comes in the future.
Learn more about the Ruby's
Fiber Scheduler feature.
Installation
gem install fiber_scheduler
Requires Ruby 3.1.
Highlights
- Enables asynchronous (colorless) programming in Ruby.
- Killer feature: full compatibility with any other Fiber Scheduler
implementation, including the async gem. - Not a framework: no DSL or new APIs. Can be used with just built-in Ruby
methods:
Fiber.set_scheduler
and
Fiber.schedule. - ~500 LOC of pure Ruby, no C extensions.
- No dependencies.
Setup
- With a block (recommended)
- Set
Fiber.schedulerdirectly
With a block (recommended)
rubyFiberScheduler do # Your code here, e.g. Fiber.schedule { ... } end
Recommended because:
- This approach has
full compatibility with any other Fiber Scheduler. Fiber.scheduleris automatically un-set outside the block.
Set Fiber.scheduler directly
rubyFiber.set_scheduler(FiberScheduler.new) # Your code here, e.g. Fiber.schedule { ... }
Fiber.scheduler is set until the end of the current thread, unless manually
unset with Fiber.set_scheduler(nil).
Pros:
- Uses only built-in Ruby methods
Fiber.set_schedulerandFiber.schedule.
Cons:
- No compatibility with other fiber schedulers.
Examples
Basic example
This example runs two HTTP requests in parallel:
rubyrequire "fiber_scheduler" require "open-uri" FiberScheduler do Fiber.schedule do URI.open("https://httpbin.org/delay/2") end Fiber.schedule do URI.open("https://httpbin.org/delay/2") end end
Advanced example
This example runs various operations in parallel. The example total running
time is slightly more than 2 seconds, which indicates all the operations ran in
parallel.
Note that all the operations used in Fiber.schedule blocks below are either
common gems or built-in Ruby methods. They all work asynchronously with this
library, no monkey patching!
rubyrequire "fiber_scheduler" require "httparty" require "open-uri" require "redis" require "sequel" DB = Sequel.postgres Sequel.extension(:fiber_concurrency) FiberScheduler do Fiber.schedule do # This HTTP request takes 2 seconds (slightly more because of the latency) URI.open("https://httpbin.org/delay/2") end Fiber.schedule do # Use any HTTP library HTTParty.get("https://httpbin.org/delay/2") end Fiber.schedule do # Works with any TCP protocol library Redis.new.blpop("abc123", 2) end Fiber.schedule do # Make database queries DB.run("SELECT pg_sleep(2)") end Fiber.schedule do sleep 2 end Fiber.schedule do # Run system commands `sleep 2` end end
Scaling example
Easily run thousands and thousands of blocking operations in parallel. This
program finishes in about 2.5 seconds.
rubyrequire "fiber_scheduler" FiberScheduler do 10_000.times do Fiber.schedule do sleep 2 end end end
Gotcha: be careful about the overheads when scaling things. The below snippet
runs sleep which is an "inexpensive" operation. But, if we were to run
thousands of network requests there would be more overhead (establishing
TCP connections, SSL handshakes etc) which would prolong program running time.
Nested Fiber.schedule example
It's possible to nest Fiber.schedule blocks arbitrarily deep.
All the sleep operations in this snippet run in parallel and the program
finishes in 2 seconds total.
rubyrequire "fiber_scheduler" FiberScheduler do Fiber.schedule do Fiber.schedule do sleep 2 end Fiber.schedule do sleep 2 end sleep 2 end Fiber.schedule do sleep 2 end end
Waiting Fiber.schedule example
Sometimes it's conventient for the parent to wait on the child fiber to
complete. Use Fiber.schedule(:waiting) to achieve that.
In the below example fiber labeled parent will wait for the child fiber to
complete. Note that only the parent fiber waits, other fibers run as usual.
This example takes 4 seconds to finish.
rubyrequire "fiber_scheduler" FiberScheduler do Fiber.schedule do # parent Fiber.schedule(:waiting) do # child sleep 2 end # The fiber stops here until the waiting child fiber completes. sleep 2 end Fiber.schedule do sleep 2 end end
Blocking Fiber.schedule example
Blocking fibers "block" all the other fibers from running until they're
finished.
This example takes 4 seconds to finish.
rubyrequire "fiber_scheduler" FiberScheduler do Fiber.schedule do Fiber.schedule(:blocking) do sleep 2 end end Fiber.schedule do sleep 2 end end
Volatile Fiber.schedule example
Volatile fibers end when all the "durable" fibers finish.
Volatile fibers (by design) may not complete all their work.
This is useful if you have a neverending task that performs some
cleanup work that should finish when the rest of the program completes.
This example takes 2 seconds to finish.
rubyrequire "fiber_scheduler" FiberScheduler do Fiber.schedule(:volatile) do # This fiber will live for only 2 seconds. loop do cleanup_work # this method will run only once sleep 10 end end Fiber.schedule do sleep 2 end end
Compatibility with other fiber schedulers
async gem
async is an awesome asynchronous programming library, if not a framework.
If async is like Rails, then fiber_scheduler is plain Ruby.
fiber_scheduler is fully compatible with async:
rubyAsync do |task| task.async do # code ... end FiberScheduler do Fiber.schedule do # code ... end end # ... end
Note that currently the opposite doesn't work:
rubyFiberScheduler do Async do # ... end Fiber.schedule do # No scheduler is available! (RuntimeError) # ... end end
Other Fiber Scheduler implementations
fiber_scheduler gem works with any other Fiber Scheduler class (current and
future ones). Example:
rubyFiber.set_scheduler(AnotherScheduler.new) # stuff FiberScheduler do # works just fine end # more stuff
fiber_scheduler is like choosing pure Ruby: it's a safe choice because you
know it works and will continue working with everything else in Ruby's
asynchronous eco-system.
Performance
This basic perf benchmark looks promising.
HINT: make sure to install io-event gem alongside fiber_scheduler for a
performance improvement.
Credits
Samuel Williams for:
- Implementing Ruby's Fiber Scheduler hooks.
- The default selector used in this gem.
License
Contributors
Showing top 2 contributors by commit count.
