mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Update NEWS & documentation relating to scheduler.
This commit is contained in:
parent
70f08f1eed
commit
f7aa51b2b8
Notes:
git
2020-09-21 12:28:34 +09:00
3 changed files with 171 additions and 140 deletions
47
NEWS.md
47
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
|
||||
|
|
137
doc/fiber.rdoc
137
doc/fiber.rdoc
|
@ -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.
|
127
doc/scheduler.md
Normal file
127
doc/scheduler.md
Normal file
|
@ -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.
|
Loading…
Reference in a new issue