ruby/ruby
Fibers and coroutines
Fibers are cooperative coroutines: lightweight, single-threaded, manually-scheduled stacks that pass control to each other via Fiber#resume and Fiber.yield. CRuby implements them by saving and restoring CPU registers and stack pointers in hand-written assembly under coroutine/.
Purpose
- Provide
Fiber.new,Fiber#resume,Fiber.yield, andFiber#raise. - Power generators, lazy enumerators, and cooperative IO.
- Underpin the Fiber Scheduler interface for asynchronous IO without OS threads.
- Run with much lower overhead than OS threads (each Fiber's stack starts at ~32 KB, growable on demand).
Files
| Path | Purpose |
|---|---|
cont.c |
High-level Fiber + Continuation implementation. ~111 KB. |
coroutine/ |
Per-architecture context-switch assembly. |
coroutine/<arch>/Context.S |
The actual register save/restore. |
coroutine/<arch>/Context.h |
C-side struct definition. |
ext/fiber/ |
The pure-Ruby Fiber-related extensions (e.g., Fiber::Pool). |
scheduler.c |
The Fiber Scheduler interface. |
Coroutine implementations
coroutine/ contains ~15 architecture/ABI variants:
| Directory | Target |
|---|---|
coroutine/x86/ |
i386 (System V) |
coroutine/amd64/ |
x86_64 System V (Linux, BSD, macOS) |
coroutine/arm32/ |
32-bit ARM (EABI) |
coroutine/arm64/ |
AArch64 (Linux, macOS) |
coroutine/ppc64le/ |
64-bit PowerPC (little-endian) |
coroutine/riscv64/ |
RISC-V 64-bit |
coroutine/s390x/ |
IBM z/Architecture |
coroutine/loongarch64/ |
LoongArch |
coroutine/mips64/ |
MIPS64 |
coroutine/sparc64/ |
SPARC v9 |
coroutine/win32/ |
i386 Windows |
coroutine/win64/ |
x86_64 Windows |
coroutine/copy/ |
Generic stack-copying fallback (slow but portable) |
coroutine/ucontext/ |
POSIX ucontext_t fallback |
coroutine/emscripten/ |
WebAssembly via Emscripten |
Each Context.S defines two routines:
coroutine_transfer(from, to)— save callee-saved registers and SP intofrom, restore fromto.coroutine_initialize(context, start, stack_top)— initialise a fresh stack with a callable.
The C-side header:
struct coroutine_context {
void *stack_pointer;
void *argument; /* pointer to the calling context for transfer back */
/* + arch-specific state (e.g. TLS pointer) */
};
void coroutine_initialize(struct coroutine_context *ctx, coroutine_start start, void *stack, size_t size);
struct coroutine_context *coroutine_transfer(struct coroutine_context *current, struct coroutine_context *target);The whole point is portability: pure assembly avoids the per-OS quirks of setjmp/longjmp or ucontext.
High-level Fiber API
cont.c builds Fibers on top of coroutines:
typedef struct rb_fiber_struct {
rb_context_t cont; /* shared with Continuation */
struct coroutine_context coroutine;
enum fiber_status status;
rb_execution_context_t saved_ec; /* saved when not running */
bool first_proc; /* root fiber? */
/* ... */
} rb_fiber_t;A Fiber owns:
- Its own value stack (
saved_ec.vm_stack). - Its own native stack (allocated via
mmapormalloc, depending on platform). - A coroutine context.
Fiber.new { |arg| ... } allocates the structures, initialises the coroutine to run a tiny C trampoline that calls the block, and parks it.
f.resume(arg):
- Saves the calling Fiber's EC.
- Calls
coroutine_transfer(current_ctx, fiber_ctx). - Returns whatever was passed to the matching
Fiber.yieldfrom insidef.
Fiber.yield(v) is the inverse: save current, transfer back to the caller.
sequenceDiagram
participant Caller
participant FiberCtx as Fiber's coroutine context
participant Block as Fiber block
Caller->>FiberCtx: Fiber.new { ... }
Caller->>Caller: stay running
Caller->>FiberCtx: f.resume(arg)
FiberCtx->>Block: invoke block(arg)
Block->>FiberCtx: Fiber.yield(v)
FiberCtx->>Caller: f.resume returns v
Caller->>FiberCtx: f.resume(arg2)
FiberCtx->>Block: yield returns arg2
Block-->>FiberCtx: block returns final
FiberCtx-->>Caller: f.resume returns finalFiber Scheduler
scheduler.c implements the Fiber Scheduler interface. Setting one (Fiber.set_scheduler(s)) makes Ruby's blocking IO calls cooperate with s:
IO#read/#writeask the schedulerio_wait(io, events, timeout)instead of blocking.Kernel#sleepcallskernel_sleep(duration).Process.waitcallsprocess_wait(pid, flags).Mutex#lock,ConditionVariable#wait,Queue#poproute throughblock/unblock.
The most common scheduler is the async gem, which provides an event-loop-driven scheduler that lets thousands of IO-bound Fibers coexist on a single thread.
The C side calls rb_scheduler_* helpers (in scheduler.c) that:
- Check
Fiber.schedulerfor the current Ractor. - If set, call into the Ruby-level scheduler.
- If not, fall back to a blocking syscall (with GVL release).
Continuations (legacy)
cont.c also implements Continuation — full call/cc, captured via Continuation.new. Continuations save the entire C stack at capture time and restore it on #call. They're slow, leak-prone, and discouraged since 1.9; Fibers are the modern replacement. The implementation still ships because some legacy code uses it.
require 'continuation' is required to make Continuation.new work — the class isn't autoloaded.
Stack size
Default Fiber stacks are ~32 KB (configurable via RUBY_FIBER_VM_STACK_SIZE and RUBY_FIBER_MACHINE_STACK_SIZE env vars). The VM stack and the native (machine) stack are sized separately because the VM stack stores VALUEs and grows differently from the C stack.
Stacks are allocated lazily and reused via a per-Ractor pool. Killed Fibers' stacks are recycled instead of munmapped.
Performance characteristics
- Fiber creation: hundreds of nanoseconds.
- Fiber switch: tens of nanoseconds (a single
coroutine_transferplus EC save/restore). - Memory: ~32 KB native stack + ~4 KB VM stack per fiber.
This makes 100,000+ concurrent Fibers feasible — far more than threads, where the OS itself caps you at much smaller numbers.
Entry points for modification
- New architecture support: add
coroutine/<arch>/Context.{h,S}, register it incoroutine/Makefile.inand the configure script. - Bug in resume/yield:
cont.c::fiber_switchis the central handoff. The arch assembly is correct in isolation; bugs are usually in the EC save/restore. - Scheduler interface change: extend
scheduler.cand document on the public Ruby API. - Stack tuning: env vars are read in
cont.c::fiber_machine_stack_size/fiber_vm_stack_size.
See threading.md for OS threads and systems/io.md for how IO calls hand off to the scheduler.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.