Fibers are primitives for implementing light weight cooperative concurrency in Ruby. Basically they are a means of creating code blocks that can be paused and resumed, much like threads. The main difference is that they are never preempted and that the scheduling must be done by the programmer and not the VM.
As opposed to other stackless light weight concurrency models, each fiber comes with a stack. This enables the fiber to be paused from deeply nested function calls within the fiber block. See the ruby(1) manpage to configure the size of the fiber stack(s).
When a fiber is created it will not run automatically. Rather it must be explicitly asked to run using the #resume method. The code running inside the fiber can give up control by calling ::yield in which case it yields control back to caller (the caller of the #resume).
Upon yielding or termination the Fiber returns the value of the last executed expression
For instance:
fiber = Fiber.new do Fiber.yield 1 2 end puts fiber.resume puts fiber.resume puts fiber.resume
produces
1 2 FiberError: dead fiber called
The #resume method accepts an arbitrary number of parameters, if it is the first call to resume then they will be passed as block arguments. Otherwise they will be the return value of the call to ::yield
Example:
fiber = Fiber.new do |first| second = Fiber.yield first + 2 end puts fiber.resume 10 puts fiber.resume 1_000_000 puts fiber.resume "The fiber will be dead before I can cause trouble"
produces
12 1000000 FiberError: dead fiber called
Returns the current fiber. You need to require 'fiber'
before
using this method. If you are not running in the context of a fiber this
method will return the root fiber.
static VALUE rb_fiber_s_current(VALUE klass) { return rb_fiber_current(); }
Yields control back to the context that resumed the fiber, passing along any arguments that were passed to it. The fiber will resume processing at this point when resume is called next. Any arguments passed to the next resume will be the value that this ::yield expression evaluates to.
static VALUE rb_fiber_s_yield(int argc, VALUE *argv, VALUE klass) { return rb_fiber_yield_kw(argc, argv, PASS_KW_SPLAT); }
Returns true if the fiber can still be resumed (or transferred to). After
finishing execution of the fiber block this method will always return
false. You need to require 'fiber'
before using this method.
VALUE rb_fiber_alive_p(VALUE fiber_value) { return FIBER_TERMINATED_P(fiber_ptr(fiber_value)) ? Qfalse : Qtrue; }
Raises an exception in the fiber at the point at which the last ::yield was called, or at the start if
neither resume
nor raise
were called before.
With no arguments, raises a RuntimeError
. With a single
String
argument, raises a RuntimeError
with the
string as a message. Otherwise, the first parameter should be the name of
an Exception
class (or an object that returns an
Exception
object when sent an exception
message).
The optional second parameter sets the message associated with the
exception, and the third parameter is an array of callback information.
Exceptions are caught by the rescue
clause of
begin...end
blocks.
static VALUE rb_fiber_raise(int argc, VALUE *argv, VALUE fiber) { VALUE exc = rb_make_exception(argc, argv); return rb_fiber_resume_kw(fiber, -1, &exc, RB_NO_KEYWORDS); }
Resumes the fiber from the point at which the last ::yield was called, or starts running it if it is the first call to resume. Arguments passed to resume will be the value of the ::yield expression or will be passed as block parameters to the fiber’s block if this is the first resume.
Alternatively, when resume is called it evaluates to the arguments passed to the next ::yield statement inside the fiber’s block or to the block value if it runs to completion without any ::yield
static VALUE rb_fiber_m_resume(int argc, VALUE *argv, VALUE fiber) { return rb_fiber_resume_kw(fiber, argc, argv, PASS_KW_SPLAT); }
Returns fiber information string.
static VALUE fiber_to_s(VALUE fiber_value) { const rb_fiber_t *fiber = fiber_ptr(fiber_value); const rb_proc_t *proc; char status_info[0x20]; if (fiber->transferred) { snprintf(status_info, 0x20, " (%s, transferred)", fiber_status_name(fiber->status)); } else { snprintf(status_info, 0x20, " (%s)", fiber_status_name(fiber->status)); } if (!rb_obj_is_proc(fiber->first_proc)) { VALUE str = rb_any_to_s(fiber_value); strlcat(status_info, ">", sizeof(status_info)); rb_str_set_len(str, RSTRING_LEN(str)-1); rb_str_cat_cstr(str, status_info); return str; } GetProcPtr(fiber->first_proc, proc); return rb_block_to_s(fiber_value, &proc->block, status_info); }
Transfer control to another fiber, resuming it from where it last stopped
or starting it if it was not resumed before. The calling fiber will be
suspended much like in a call to ::yield. You need to require
'fiber'
before using this method.
The fiber which receives the transfer call is treats it much like a resume call. Arguments passed to transfer are treated like those passed to resume.
You cannot call resume
on a fiber that has been transferred
to. If you call transfer
on a fiber, and later call
resume
on the the fiber, a FiberError
will be
raised. Once you call transfer
on a fiber, the only way to
resume processing the fiber is to call transfer
on it again.
Example:
fiber1 = Fiber.new do puts "In Fiber 1" Fiber.yield puts "In Fiber 1 again" end fiber2 = Fiber.new do puts "In Fiber 2" fiber1.transfer puts "Never see this message" end fiber3 = Fiber.new do puts "In Fiber 3" end fiber2.resume fiber3.resume fiber1.resume rescue (p $!) fiber1.transfer
produces
In Fiber 2 In Fiber 1 In Fiber 3 #<FiberError: cannot resume transferred Fiber> In Fiber 1 again
static VALUE rb_fiber_m_transfer(int argc, VALUE *argv, VALUE fiber_value) { rb_fiber_t *fiber = fiber_ptr(fiber_value); fiber->transferred = 1; return fiber_switch(fiber, argc, argv, 0, PASS_KW_SPLAT); }