# Scheduler

The scheduler interface is used to intercept blocking operations. A typical
implementation would be a wrapper for a gem like `EventMachine` or `Async`. This
design provides separation of concerns between the event loop implementation
and application code. It also allows for layered schedulers which can perform
instrumentation.

## Interface

This is the interface you need to implement.

~~~ ruby
class Scheduler
  # Wait for the given file descriptor to match the specified events within
  # the specified timeout.
  # @parameter event [Integer] A bit mask of `IO::READABLE`,
  #   `IO::WRITABLE` and `IO::PRIORITY`.
  # @parameter timeout [Numeric] The amount of time to wait for the event in seconds.
  # @returns [Integer] The subset of events that are ready.
  def io_wait(io, events, timeout)
  end

  # Sleep the current task for the specified duration, or forever if not
  # specified.
  # @param duration [Numeric] The amount of time to sleep in seconds.
  def kernel_sleep(duration = nil)
  end

  # Block the calling fiber.
  # @parameter blocker [Object] What we are waiting on, informational only.
  # @parameter timeout [Numeric | Nil] The amount of time to wait for in seconds.
  # @returns [Boolean] Whether the blocking operation was successful or not.
  def block(blocker, timeout = nil)
  end

  # Unblock the specified fiber.
  # @parameter blocker [Object] What we are waiting on, informational only.
  # @parameter fiber [Fiber] The fiber to unblock.
  # @reentrant Thread safe.
  def unblock(blocker, fiber)
  end

  # Intercept the creation of a non-blocking fiber.
  # @returns [Fiber]
  def fiber(&block)
    Fiber.new(blocking: false, &block)
  end

  # Invoked when the thread exits.
  def close
    self.run
  end

  def run
    # Implement event loop here.
  end
end
~~~

Additional hooks may be introduced in the future, we will use feature detection
in order to enable these hooks.

## Non-blocking Execution

The scheduler hooks will only be used in special non-blocking execution
contexts. Non-blocking execution contexts introduce non-determinism because the
execution of scheduler hooks may introduce context switching points into your
program.

### Fibers

Fibers can be used to create non-blocking execution contexts.

~~~ ruby
Fiber.new(blocking: false) do
  puts Fiber.current.blocking? # false

  # May invoke `Thread.scheduler&.io_wait`.
  io.read(...)

  # May invoke `Thread.scheduler&.io_wait`.
  io.write(...)

  # Will invoke `Thread.scheduler&.kernel_sleep`.
  sleep(n)
end.resume
~~~

We also introduce a new method which simplifies the creation of these
non-blocking fibers:

~~~ ruby
Fiber.schedule do
  puts Fiber.current.blocking? # false
end
~~~

The purpose of this method is to allow the scheduler to internally decide the
policy for when to start the fiber, and whether to use symmetric or asymmetric
fibers.

### IO

By default, I/O is non-blocking. Not all operating systems support non-blocking
I/O. Windows is a notable example where socket I/O can be non-blocking but pipe
I/O is blocking. Provided that there *is* a scheduler and the current thread *is
non-blocking*, the operation will invoke the scheduler.

### Mutex

The `Mutex` class can be used in a non-blocking context and is fiber specific.

### ConditionVariable

The `ConditionVariable` class can be used in a non-blocking context and is
fiber-specific.

### Queue / SizedQueue

The `Queue` and `SizedQueue` classses can be used in a non-blocking context and
are fiber-specific.

### Thread

The `Thread#join` operation can be used in a non-blocking context and is
fiber-specific.
