class Fiber - RDoc Documentation

# class Fiber

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 `Fiber#resume` method. The code running inside the fiber can give up control by calling `Fiber.yield` in which case it yields control back to caller (the caller of the `Fiber#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 `Fiber#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 `Fiber.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

## Non-blocking Fibers¶ ↑

Since Ruby 3.0, the concept of non-blocking fiber was introduced. Non-blocking fiber, when reaching any potentially blocking operation (like sleep, wait for another process, wait for I/O data to be ready), instead of just freezing itself and all execution in the thread, yields control to other fibers, and allows the scheduler to handle waiting and waking (resuming) the fiber when it can proceed.

For `Fiber` to behave as non-blocking, it should be created in `Fiber.new` with `blocking: false` (which is the default now), and `Fiber.scheduler` should be set with `Fiber.set_scheduler`. If `Fiber.scheduler` is not set in the current thread, blocking and non-blocking fiber's behavior is identical.

Ruby doesn't provide a scheduler class: it is expected to be implemented by the user and correspond to `Fiber::SchedulerInterface`.

There is also `Fiber.schedule` method, which is expected to immediately perform passed block in a non-blocking manner (but its actual implementation is up to the scheduler).

### Public Class Methods

blocking? → false or number click to toggle source

Returns `false` if the current fiber is non-blocking. `Fiber` is non-blocking if it was created via passing `blocking: false` to `Fiber.new`, or via `Fiber.schedule`.

If the current `Fiber` is blocking, the method, unlike usual predicate methods, returns a number of blocking fibers currently running (TBD: always 1?).

Note, that even if the method returns `false`, `Fiber` behaves differently only if `Fiber.scheduler` is set in the current thread.

See the “Non-blocking fibers” section in class docs for details.

    static VALUE
    rb_f_fiber_blocking_p(VALUE klass)
    {
        rb_thread_t *thread = GET_THREAD();
        unsigned blocking = thread->blocking;

        if (blocking == 0)
            return Qfalse;

        return INT2NUM(blocking);
    }

current() → fiber click to toggle source

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();
    }

new(blocking: false) { |*args| ... } → fiber click to toggle source

Creates new `Fiber`. Initially, fiber is not running, but can be resumed with `resume`. Arguments to the first `resume` call would be passed to the block:

    f = Fiber.new do |initial|
       current = initial
       loop do
         puts "current: #{current.inspect}"
         current = Fiber.yield
       end
    end
    f.resume(100)     # prints: current: 100
    f.resume(1, 2, 3) # prints: current: [1, 2, 3]
    f.resume          # prints: current: nil
    # ... and so on ...

if `blocking: false` is passed to the `Fiber.new`, and current thread has `Fiber.scheduler` defined, the `Fiber` becomes non-blocking (see “Non-blocking fibers” section in class docs).

    static VALUE
    rb_fiber_initialize(int argc, VALUE* argv, VALUE self)
    {
        return rb_fiber_initialize_kw(argc, argv, self, rb_keyword_given_p());
    }

schedule { |*args| ... } → fiber click to toggle source

The method is expected to immediately run the provided block of code in a separate non-blocking fiber.

    puts "Go to sleep!"

    Fiber.set_scheduler(MyScheduler.new)

    Fiber.schedule do
      puts "Going to sleep"
      sleep(1)
      puts "I slept well"
    end

    puts "Wakey-wakey, sleepyhead"

Assuming MyScheduler is properly implemented, this program will produce:

    Go to sleep!
    Going to sleep
    Wakey-wakey, sleepyhead
    ...1 sec pause here...
    I slept well

…e.g. on the first blocking operation inside the `Fiber` (`sleep(1)`), the control is yielded at the outside code (main fiber), and at the end of the execution, the scheduler takes care of properly resuming all the blocked fibers.

Note that the behavior described above is how the method is expected to behave, actual behavior is up to the current scheduler's implementation of `Fiber::SchedulerInterface#fiber` method. Ruby doesn't enforce this method to behave in any particular way.

If the scheduler is not set, the method raises `RuntimeError (No scheduler is available!)`.

    static VALUE
    rb_f_fiber(int argc, VALUE *argv, VALUE obj)
    {
        return rb_f_fiber_kw(argc, argv, rb_keyword_given_p());
    }

scheduler → obj or nil click to toggle source

`Fiber` scheduler, set in the current thread with `Fiber.set_scheduler`. If the scheduler is `nil` (which is the default), non-blocking fibers behavior is the same as blocking. (see “Non-blocking fibers” section in class docs for details about the scheduler concept).

    static VALUE
    rb_fiber_scheduler(VALUE klass)
    {
        return rb_scheduler_get();
    }

set_scheduler(scheduler) → scheduler click to toggle source

Sets `Fiber` scheduler for the current thread. If the scheduler is set, non-blocking fibers (created by `Fiber.new` with `blocking: false`, or by `Fiber.schedule`) call that scheduler's hook methods on potentially blocking operations, and the current thread will call scheduler's `close` method on finalization (allowing the scheduler to properly manage all non-finished fibers).

`scheduler` can be an object of any class corresponding to `Fiber::SchedulerInterface`. Its implementation is up to the user.

See also the “Non-blocking fibers” section in class docs.

    static VALUE
    rb_fiber_set_scheduler(VALUE klass, VALUE scheduler)
    {
        // if (rb_scheduler_get() != Qnil) {
        //     rb_raise(rb_eFiberError, "Scheduler is already defined!");
        // }

        return rb_scheduler_set(scheduler);
    }

yield(args, ...) → obj click to toggle source

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 `Fiber.yield` expression evaluates to.

    static VALUE
    rb_fiber_s_yield(int argc, VALUE *argv, VALUE klass)
    {
        return rb_fiber_yield_kw(argc, argv, rb_keyword_given_p());
    }

### Public Instance Methods

alive? → true or false click to toggle source

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;
    }

backtrace → array click to toggle source
backtrace(start) → array
backtrace(start, count) → array
backtrace(start..end) → array

Returns the current execution stack of the fiber. `start`, `count` and `end` allow to select only parts of the backtrace.

    def level3
      Fiber.yield
    end

    def level2
      level3
    end

    def level1
      level2
    end

    f = Fiber.new { level1 }

    # It is empty before the fiber started
    f.backtrace
    #=> []

    f.resume

    f.backtrace
    #=> ["test.rb:2:in `yield'", "test.rb:2:in `level3'", "test.rb:6:in `level2'", "test.rb:10:in `level1'", "test.rb:13:in `block in <main>'"]
    p f.backtrace(1) # start from the item 1
    #=> ["test.rb:2:in `level3'", "test.rb:6:in `level2'", "test.rb:10:in `level1'", "test.rb:13:in `block in <main>'"]
    p f.backtrace(2, 2) # start from item 2, take 2
    #=> ["test.rb:6:in `level2'", "test.rb:10:in `level1'"]
    p f.backtrace(1..3) # take items from 1 to 3
    #=> ["test.rb:2:in `level3'", "test.rb:6:in `level2'", "test.rb:10:in `level1'"]

    f.resume

    # It is nil after the fiber is finished
    f.backtrace
    #=> nil

    static VALUE
    rb_fiber_backtrace(int argc, VALUE *argv, VALUE fiber)
    {
        return rb_vm_backtrace(argc, argv, &fiber_ptr(fiber)->cont.saved_ec);
    }

backtrace_locations → array click to toggle source
backtrace_locations(start) → array
backtrace_locations(start, count) → array
backtrace_locations(start..end) → array

Like `backtrace`, but returns each line of the execution stack as a `Thread::Backtrace::Location`. Accepts the same arguments as `backtrace`.

    f = Fiber.new { Fiber.yield }
    f.resume
    loc = f.backtrace_locations.first
    loc.label  #=> "yield"
    loc.path   #=> "test.rb"
    loc.lineno #=> 1

    static VALUE
    rb_fiber_backtrace_locations(int argc, VALUE *argv, VALUE fiber)
    {
        return rb_vm_backtrace_locations(argc, argv, &fiber_ptr(fiber)->cont.saved_ec);
    }

blocking? → true or false click to toggle source

Returns `true` if `fiber` is blocking and `false` otherwise. `Fiber` is non-blocking if it was created via passing `blocking: false` to `Fiber.new`, or via `Fiber.schedule`.

Note, that even if the method returns `false`, `Fiber` behaves differently only if `Fiber.scheduler` is set in the current thread.

See the “Non-blocking fibers” section in class docs for details.

    VALUE
    rb_fiber_blocking_p(VALUE fiber)
    {
        return (fiber_ptr(fiber)->blocking == 0) ? Qfalse : Qtrue;
    }

Returns fiber information string.

Alias for: to_s
raise → obj click to toggle source
raise(string) → obj
raise(exception [, string [, array]]) → obj

Raises an exception in the fiber at the point at which the last `Fiber.yield` was called. If the fiber has not been started or has already run to completion, raises `FiberError`. If the fiber is yielding, it is resumed. If it is transferring, it is transferred into. But if it is resuming, raises `FiberError`.

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)
    {
        rb_fiber_t *fiber = fiber_ptr(fiber_value);
        VALUE exc = rb_make_exception(argc, argv);
        if (RTEST(fiber->resuming_fiber)) {
            rb_raise(rb_eFiberError, "attempt to raise a resuming fiber");
        }
        else if (FIBER_SUSPENDED_P(fiber) && !fiber->yielding) {
            return rb_fiber_transfer_kw(fiber_value, -1, &exc, RB_NO_KEYWORDS);
        }
        else {
            return rb_fiber_resume_kw(fiber_value, -1, &exc, RB_NO_KEYWORDS);
        }
    }

resume(args, ...) → obj click to toggle source

Resumes the fiber from the point at which the last `Fiber.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 `Fiber.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 `Fiber.yield` statement inside the fiber's block or to the block value if it runs to completion without any `Fiber.yield`

    static VALUE
    rb_fiber_m_resume(int argc, VALUE *argv, VALUE fiber)
    {
        return rb_fiber_resume_kw(fiber, argc, argv, rb_keyword_given_p());
    }

to_s → string click to toggle source

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 (RTEST(fiber->resuming_fiber)) {
            snprintf(status_info, 0x20, " (%s by resuming)", 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);
    }

Also aliased as: inspect
transfer(args, ...) → obj click to toggle source

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 `Fiber.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.

The two style of control passing to and from fiber (one is `resume` and `Fiber::yield`, another is `transfer` to and from fiber) can't be freely mixed.

- If the Fiber's lifecycle had started with transfer, it will never be able to yield or be resumed control passing, only finish or transfer back. (It still can resume other fibers that are allowed to be resumed.)

- If the Fiber's lifecycle had started with resume, it can yield or transfer to another `Fiber`, but can receive control back only the way compatible with the way it was given away: if it had transferred, it only can be transferred back, and if it had yielded, it only can be resumed back. After that, it again can transfer or yield.

If those rules are broken `FiberError` is raised.

For an individual `Fiber` design, yield/resume is more easy to use style (the `Fiber` just gives away control, it doesn't need to think about who the control is given to), while transfer is more flexible for complex cases, allowing to build arbitrary graphs of Fibers dependent on each other.

Example:

    require 'fiber'

    manager = nil # For local var to be visible inside worker block

    # This fiber would be started with transfer
    # It can't yield, and can't be resumed
    worker = Fiber.new { |work|
      puts "Worker: starts"
      puts "Worker: Performed #{work.inspect}, transferring back"
      # Fiber.yield     # this would raise FiberError: attempt to yield on a not resumed fiber
      # manager.resume  # this would raise FiberError: attempt to resume a resumed fiber (double resume)
      manager.transfer(work.capitalize)
    }

    # This fiber would be started with resume
    # It can yield or transfer, and can be transferred
    # back or resumed
    manager = Fiber.new {
      puts "Manager: starts"
      puts "Manager: transferring 'something' to worker"
      result = worker.transfer('something')
      puts "Manager: worker returned #{result.inspect}"
      # worker.resume    # this would raise FiberError: attempt to resume a transferring fiber
      Fiber.yield        # this is OK, the fiber transferred from and to, now it can yield
      puts "Manager: finished"
    }

    puts "Starting the manager"
    manager.resume
    puts "Resuming the manager"
    # manager.transfer  # this would raise FiberError: attempt to transfer to a yielding fiber
    manager.resume

produces

    Starting the manager
    Manager: starts
    Manager: transferring 'something' to worker
    Worker: starts
    Worker: Performed "something", transferring back
    Manager: worker returned "Something"
    Resuming the manager
    Manager: finished

    static VALUE
    rb_fiber_m_transfer(int argc, VALUE *argv, VALUE fiber_value)
    {
        return rb_fiber_transfer_kw(fiber_value, argc, argv, rb_keyword_given_p());
    }