summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS.md47
-rw-r--r--doc/fiber.rdoc137
-rw-r--r--doc/scheduler.md127
3 files changed, 171 insertions, 140 deletions
diff --git a/NEWS.md b/NEWS.md
index 5a43a7a5d3..e6c77e0977 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -161,11 +161,51 @@ Outstanding ones only.
p C.ancestors #=> [C, M1, M2, Object, Kernel, BasicObject]
```
+* Thread
+
+ * Introduce `Thread#scheduler` for intercepting blocking operations and
+ `Thread.scheduler` for accessing the current scheduler. See
+ doc/scheduler.md for more details. [[Feature #16786]]
+ * `Thread#blocking?` tells whether the current execution context is
+ blocking. [[Feature #16786]]
+ * `Thread#join` invokes the scheduler hooks `block`/`unblock` in a
+ non-blocking execution context. [[Feature #16786]]
+
* Mutex
- * Mutex is now acquired per-Fiber instead of per-Thread. This change should
- be compatible for essentially all usages and avoids blocking when using
- a Fiber Scheduler. [[Feature #16792]]
+ * `Mutex` is now acquired per-`Fiber` instead of per-`Thread`. This change
+ should be compatible for essentially all usages and avoids blocking when
+ using a scheduler. [[Feature #16792]]
+
+* Fiber
+
+ * `Fiber.new(blocking: true/false)` allows you to create non-blocking
+ execution contexts. [[Feature #16786]]
+ * `Fiber#blocking?` tells whether the fiber is non-blocking. [[Feature #16786]]
+
+* Kernel
+
+ * `Kernel.sleep(...)` invokes the scheduler hook `#kernel_sleep(...)` in a
+ non-blocking execution context. [[Feature #16786]]
+
+* IO
+
+ * `IO#nonblock?` now defaults to `true`. [[Feature #16786]]
+ * `IO#wait_readable`, `IO#wait_writable`, `IO#read`, `IO#write` and other
+ related methods (e.g. `#puts`, `#gets`) may invoke the scheduler hook
+ `#io_wait(io, events, timeout)` in a non-blocking execution context.
+ [[Feature #16786]]
+
+* ConditionVariable
+
+ * `ConditionVariable#wait` may now invoke the `block`/`unblock` scheduler
+ hooks in a non-blocking context. [[Feature #16786]]
+
+* Queue / SizedQueue
+
+ * `Queue#pop`, `SizedQueue#push` and related methods may now invoke the
+ `block`/`unblock` scheduler hooks in a non-blocking context.
+ [[Feature #16786]]
* Ractor
@@ -381,6 +421,7 @@ Excluding feature bug fixes.
[Feature #16686]: https://bugs.ruby-lang.org/issues/16686
[Feature #16746]: https://bugs.ruby-lang.org/issues/16746
[Feature #16754]: https://bugs.ruby-lang.org/issues/16754
+[Feature #16786]: https://bugs.ruby-lang.org/issues/16786
[Feature #16792]: https://bugs.ruby-lang.org/issues/16792
[Feature #16828]: https://bugs.ruby-lang.org/issues/16828
[Misc #16961]: https://bugs.ruby-lang.org/issues/16961
diff --git a/doc/fiber.rdoc b/doc/fiber.rdoc
deleted file mode 100644
index 584e67ffca..0000000000
--- a/doc/fiber.rdoc
+++ /dev/null
@@ -1,137 +0,0 @@
-= Fiber
-
-Fiber is a flow-control primitive which enable cooperative scheduling. This is
-in contrast to threads which can be preemptively scheduled at any time. While
-having a similar memory profiles, the cost of context switching fibers can be
-significantly less than threads as it does not involve a system call.
-
-== Design
-
-=== Scheduler
-
-The per-thread fiber 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.
-
- class Scheduler
- # Wait for the given file descriptor to become readable.
- def wait_readable(io)
- end
-
- # Wait for the given file descriptor to become writable.
- def wait_writable(io)
- end
-
- # Wait for the given file descriptor to match the specified events within
- # the specified timeout.
- # @param event [Integer] a bit mask of +IO::WAIT_READABLE+,
- # `IO::WAIT_WRITABLE` and `IO::WAIT_PRIORITY`.
- # @param timeout [#to_f] the amount of time to wait for the event.
- def wait_any(io, events, timeout)
- end
-
- # Sleep the current task for the specified duration, or forever if not
- # specified.
- # @param duration [#to_f] the amount of time to sleep.
- def wait_sleep(duration = nil)
- end
-
- # The Ruby virtual machine is going to enter a system level blocking
- # operation.
- def enter_blocking_region
- end
-
- # The Ruby virtual machine has completed the system level blocking
- # operation.
- def exit_blocking_region
- end
-
- # Intercept the creation of a non-blocking fiber.
- def fiber(&block)
- Fiber.new(blocking: false, &block)
- end
-
- # Invoked when the thread exits.
- def run
- # Implement event loop here.
- end
- end
-
-On CRuby, the following extra methods need to be implemented to handle the
-public C interface:
-
- class Scheduler
- # Wrapper for rb_wait_readable(int) C function.
- def wait_readable_fd(fd)
- wait_readable(::IO.for_fd(fd, autoclose: false))
- end
-
- # Wrapper for rb_wait_readable(int) C function.
- def wait_writable_fd(fd)
- wait_writable(::IO.for_fd(fd, autoclose: false))
- end
-
- # Wrapper for rb_wait_for_single_fd(int) C function.
- def wait_for_single_fd(fd, events, duration)
- wait_any(::IO.for_fd(fd, autoclose: false), events, duration)
- end
- end
-
-=== Non-blocking Fibers
-
-By default fibers are blocking. Non-blocking fibers may invoke specific
-scheduler hooks when a blocking operation occurs, and these hooks may introduce
-context switching points.
-
- Fiber.new(blocking: false) do
- puts Fiber.current.blocking? # false
-
- # May invoke `Thread.current.scheduler&.wait_readable`.
- io.read(...)
-
- # May invoke `Thread.current.scheduler&.wait_writable`.
- io.write(...)
-
- # Will invoke `Thread.current.scheduler&.wait_sleep`.
- sleep(n)
- end.resume
-
-We also introduce a new method which simplifies the creation of these
-non-blocking fibers:
-
- 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.
-
-=== Mutex
-
-Locking a mutex causes the +Thread#scheduler+ to not be used while the mutex
-is held by that thread. On +Mutex#lock+, fiber switching via the scheduler
-is disabled and operations become blocking for all fibers of the same +Thread+.
-On +Mutex#unlock+, the scheduler is enabled again.
-
- mutex = Mutex.new
-
- puts Thread.current.blocking? # 1 (true)
-
- Fiber.new(blocking: false) do
- puts Thread.current.blocking? # false
- mutex.synchronize do
- puts Thread.current.blocking? # (1) true
- end
-
- puts Thread.current.blocking? # false
- end.resume
-
-=== Non-blocking I/O
-
-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.
diff --git a/doc/scheduler.md b/doc/scheduler.md
new file mode 100644
index 0000000000..e641dabcba
--- /dev/null
+++ b/doc/scheduler.md
@@ -0,0 +1,127 @@
+# 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.