The Fiber scheduler lets Ruby run thousands of concurrent I/O operations with ordinary blocking-style code.
A Fiber is a lightweight coroutine: it has its own stack, runs when you call resume, and pauses itself with Fiber.yield, handing a value back. Fibers are cooperatively scheduled, cost far less memory than threads and switch without OS involvement. External Enumerators (e.next) are implemented with fibers.
f = Fiber.new { x = Fiber.yield(1); x * 10 }
f.resume # => 1 (paused at yield)
f.resume(5) # => 50 (yield returned 5; fiber finished)Ruby 3.0 added the fiber scheduler interface. When a non-blocking fiber is running and a scheduler has been set with Fiber.set_scheduler, blocking operations like socket and pipe reads, sleep, DNS lookups, Process.wait and Thread#join call hooks on the scheduler instead of blocking the thread. The scheduler parks that fiber, runs others, and resumes it when its I/O is ready, typically using epoll, kqueue or io_uring.
Ruby ships only the interface; implementations come from gems, most notably async (with the Falcon web server). With it you write normal sequential code inside Async do ... end tasks and get event-loop concurrency without callbacks or await keywords.
Limits: it only helps I/O that goes through Ruby's IO layer or scheduler-aware C extensions. CPU-bound work, or a C extension that blocks without releasing control, still stalls every fiber on that thread. Fibers don't run in parallel; combine with processes for multiple cores.
Example
gen = Fiber.new do
3.times { |i| Fiber.yield "step #{i}" }
"finished"
end
4.times { puts gen.resume } # step 0, step 1, step 2, finished
p gen.alive? # false
# Round-robin of cooperative tasks
tasks = %w[A B].map do |name|
Fiber.new do
2.times { |i| puts "#{name}#{i}"; Fiber.yield }
:done
end
end
until tasks.empty?
tasks.reject! { |t| t.resume == :done }
end
# A0 B0 A1 B1 (one per line)
# With the async gem (Fiber scheduler), these 3 sleeps overlap:
# require "async"
# Async { 3.times.map { Async { sleep 1 } }.each(&:wait) } # ~1s totalRemember
- Fibers are coroutines: resume runs, Fiber.yield pauses and returns a value
- The fiber scheduler turns blocking I/O calls into event-loop switches
- Ruby defines the interface; gems like async implement it
Watch out for
- Expecting fibers to speed up CPU-bound work; they never run in parallel.