Class: Fiber::SchedulerInterface
Overview
This is not an existing class, but documentation of the interface that Scheduler object should comply in order to be used as Fiber.scheduler and handle non-blocking fibers. See also the “Non-blocking fibers” section in Fiber class docs for explanations of some concepts.
Scheduler’s behavior and usage are expected to be as follows:
-
When the execution in the non-blocking Fiber reaches some blocking operation (like sleep, wait for a process, or a non-ready I/O), it calls some of the scheduler’s hook methods, listed below.
-
Scheduler somehow registers what the current fiber is waited for, and yields control to other fibers with Fiber.yield (so the fiber would be suspended while expecting its wait to end, and other fibers in the same thread can perform)
-
At the end of the current thread execution, the scheduler’s method #close is called
-
The scheduler runs into a wait loop, checking all the blocked fibers (which it has registered on hook calls) and resuming them when the awaited resource is ready (I/O ready, sleep time passed).
A typical implementation would probably rely for this closing loop on a gem like EventMachine or Async.
This way concurrent execution will be achieved in a way that is transparent for every individual Fiber’s code.
Hook methods are:
-
#io_wait
-
#process_wait
-
#kernel_sleep
-
#block and #unblock
-
(the list is expanded as Ruby developers make more methods having non-blocking calls)
When not specified otherwise, the hook implementations are mandatory: if they are not implemented, the methods trying to call hook will fail. To provide backward compatibility, in the future hooks will be optional (if they are not implemented, due to the scheduler being created for the older Ruby version, the code which needs this hook will not fail, and will just behave in a blocking fashion).
It is also strongly suggested that the scheduler implement the #fiber method, which is delegated to by Fiber.schedule.
Sample toy implementation of the scheduler can be found in Ruby’s code, in test/fiber/scheduler.rb
Instance Method Summary collapse
-
#block(blocker, timeout = nil) ⇒ Object
Invoked by methods like Thread.join, and by Mutex, to signify that current Fiber is blocked till further notice (e.g. #unblock) or till
timeout
will pass. -
#close ⇒ Object
Called when the current thread exits.
-
#fiber(&block) ⇒ Object
Implementation of the Fiber.schedule.
-
#io_wait(io, events, timeout) ⇒ Object
Invoked by IO#wait, IO#wait_readable, IO#wait_writable to ask whether the specified descriptor is ready for specified events within the specified
timeout
. -
#kernel_sleep(duration = nil) ⇒ Object
Invoked by Kernel#sleep and Mutex#sleep and is expected to provide an implementation of sleeping in a non-blocking way.
-
#process_wait(pid, flags) ⇒ Object
Invoked by Process::Status.wait in order to wait for a specified process.
-
#unblock(blocker, fiber) ⇒ Object
Invoked to wake up Fiber previously blocked with #block (for example, Mutex#lock calls #block and Mutex#unlock calls #unblock).
Instance Method Details
#block(blocker, timeout = nil) ⇒ Object
Invoked by methods like Thread.join, and by Mutex, to signify that current Fiber is blocked till further notice (e.g. #unblock) or till timeout
will pass.
blocker
is what we are waiting on, informational only (for debugging and logging). There are no guarantees about its value.
Expected to return boolean, specifying whether the blocking operation was successful or not.
2995 2996 2997 2998 |
# File 'cont.c', line 2995 static VALUE rb_fiber_scheduler_interface_block(VALUE self) { } |
#close ⇒ Object
Called when the current thread exits. The scheduler is expected to implement this method in order to allow all waiting fibers to finalize their execution.
The suggested pattern is to implement the main event loop in the #close method.
2913 2914 2915 2916 |
# File 'cont.c', line 2913 static VALUE rb_fiber_scheduler_interface_close(VALUE self) { } |
#fiber(&block) ⇒ Object
3031 3032 3033 3034 |
# File 'cont.c', line 3031 static VALUE rb_fiber_scheduler_interface_fiber(VALUE self) { } |
#io_wait(io, events, timeout) ⇒ Object
Invoked by IO#wait, IO#wait_readable, IO#wait_writable to ask whether the specified descriptor is ready for specified events within the specified timeout
.
events
is a bit mask of IO::READABLE
, IO::WRITABLE
, and IO::PRIORITY
.
Suggested implementation should register which Fiber is waiting for which resources and immediately calling Fiber.yield to pass control to other fibers. Then, in the #close method, the scheduler might dispatch all the I/O resources to fibers waiting for it.
Expected to return the subset of events that are ready immediately.
2960 2961 2962 2963 |
# File 'cont.c', line 2960 static VALUE rb_fiber_scheduler_interface_io_wait(VALUE self) { } |
#kernel_sleep(duration = nil) ⇒ Object
Invoked by Kernel#sleep and Mutex#sleep and is expected to provide an implementation of sleeping in a non-blocking way. Implementation might register the current fiber in some list of “what fiber waits till what moment”, call Fiber.yield to pass control, and then in #close resume the fibers whose wait period have ended.
2976 2977 2978 2979 |
# File 'cont.c', line 2976 static VALUE rb_fiber_scheduler_interface_kernel_sleep(VALUE self) { } |
#process_wait(pid, flags) ⇒ Object
Invoked by Process::Status.wait in order to wait for a specified process. See that method description for arguments description.
Suggested minimal implementation:
Thread.new do
Process::Status.wait(pid, flags)
end.value
This hook is optional: if it is not present in the current scheduler, Process::Status.wait will behave as a blocking method.
Expected to returns a Process::Status instance.
2936 2937 2938 2939 |
# File 'cont.c', line 2936 static VALUE rb_fiber_scheduler_interface_process_wait(VALUE self) { } |
#unblock(blocker, fiber) ⇒ Object
Invoked to wake up Fiber previously blocked with #block (for example, Mutex#lock calls #block and Mutex#unlock calls #unblock). The scheduler should use the fiber
parameter to understand which fiber is unblocked.
blocker
is what was awaited for, but it is informational only (for debugging and logging), and it is not guaranteed to be the same value as the blocker
for #block.
3013 3014 3015 3016 |
# File 'cont.c', line 3013 static VALUE rb_fiber_scheduler_interface_unblock(VALUE self) { } |