silk.io.reactor.
Wait for many native descriptors at once: one blocking call reports which ones are readable or writable, and another thread can wake the wait.
When to use#
Use a Reactor to build an event loop, such as the loop of a JavaScript runtime or a network
server, that serves many sockets, pipes, or terminals on one thread. Register each descriptor
with Reactor.register and a token of your choice, then call Reactor.wait in a loop. Give
a Waker to other threads, so that they can end a wait, for example after they finished work
for the loop.
Details#
The reactor uses epoll on Linux and kqueue on Darwin. Readiness is level-triggered: a
descriptor that stays readable is reported by each wait, until you read all its data or change
its Interest. The reactor never reads, writes, or closes your descriptors.
Reactor.make opens the kernel queue and the wake source. A Waker shares them with the
reactor, so they stay open until the reactor and every waker are dropped. Many wakes before one
wait end that wait one time.
Errors#
Each operation fails with ReactorError: the ReactorOperation and the native error code.
Gotchas#
Deregister a descriptor before you close it. On Linux, a closed descriptor that another descriptor still refers to, such as after
dup, stays registered.On Darwin, readable and writable readiness come as two events with the same token.
A token must not be
WAKE_TOKEN. The reactor uses it for the wake source.The module is empty on targets other than Darwin on x86_64 or aarch64, and Linux with GNU libc on x86_64 or aarch64.
Examples#
Wake a wait from the same thread#
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
import silk.core.option { Option }
import silk.core.result { Result }
import silk.io.reactor { ReadyEvent, Reactor, ReactorError }
effect fn program() -> i32 ! ReactorError | OutOfMemoryError ? &mut Allocator {
let mut reactor = run Reactor.make()
let waker = Reactor.waker(&reactor)
run waker.wake()
// The wake ends the wait at once, and it is not reported as an event.
let mut events = [ReadyEvent.empty(), ReadyEvent.empty()]
let count = run reactor.wait(&mut events, Option<u64>.some(1000000000))
if count != 0 {
return 1
}
// With no wake and no descriptor, a zero timeout returns at once.
let none = run reactor.wait(&mut events, Option<u64>.some(0))
if none != 0 {
return 1
}
return 0
}
pub effect fn main() -> i32 {
let mut allocator = Allocator.systemAllocatorProvider()
let result = run Effect.result(program()) |> Effect.provideMut<Allocator>(&mut allocator)
return match move result {
Result<i32, ReactorError | OutOfMemoryError>.Success { value } => value
Result<i32, ReactorError | OutOfMemoryError>.Failure { .. } => 1
}
}Import as Reactor with import silk.io.reactor { Reactor }.
Public declarations: 7.
ReactorOperation#
pub enum ReactorOperationThe operation of a Reactor or Waker that failed.
Create#
CreateOpening the kernel queue or the wake source, in Reactor.make.
Register#
RegisterAdding a descriptor, in Reactor.register.
Modify#
ModifyChanging the interest of a descriptor, in Reactor.modify.
Deregister#
DeregisterRemoving a descriptor, in Reactor.deregister.
Wait#
WaitWaiting for events, in Reactor.wait.
Wake#
WakeSignaling the wake source, in Waker.wake.
ReactorError#
pub struct ReactorErrorA failed reactor operation: which one, and the native error code.
Details#
code is the errno value of the failed system call, such as 9 (EBADF) for a descriptor that
is not open, or 17 (EEXIST) for a descriptor that is already registered on Linux.
Field operation#
pub operation: ReactorOperationThe operation that failed.
Field code#
pub code: i32The native error code.
Interest#
pub struct InterestThe readiness that a registered descriptor reports.
Details#
Make one with Interest.readOnly, Interest.writeOnly, or Interest.both. An interest
with neither flag keeps the descriptor registered but reports only errors and hangups on Linux,
and nothing on Darwin.
Field readable#
pub readable: boolReport the descriptor when a read would not block.
Field writable#
pub writable: boolReport the descriptor when a write would not block.
Implementation Copy for Interest#
impl Copy for InterestImplementation Interest#
impl InterestAssociated function readOnly#
pub fn readOnly() -> InterestReturns an interest in reads only.
Associated function writeOnly#
pub fn writeOnly() -> InterestReturns an interest in writes only.
Associated function both#
pub fn both() -> InterestReturns an interest in reads and writes.
ReadyEvent#
pub struct ReadyEventOne descriptor that Reactor.wait found ready.
Details#
token is the value given to Reactor.register. More than one flag can be set. hangup
means that the peer closed its side, or the pipe has no writer; data that was sent before can
still be readable. error means that the descriptor has a pending error: read it with the
operation that you do next, such as a read or getsockopt(SO_ERROR).
Field token#
pub token: u64The token of the descriptor.
Field readable#
pub readable: boolA read would not block.
Field writable#
pub writable: boolA write would not block.
Field hangup#
pub hangup: boolThe peer closed its side.
Field error#
pub error: boolThe descriptor has a pending error.
Implementation Copy for ReadyEvent#
impl Copy for ReadyEventImplementation ReadyEvent#
impl ReadyEventAssociated function empty#
pub fn empty() -> ReadyEventReturns an event with token 0 and no flags, to fill an event buffer before a wait.
Reactor#
pub struct ReactorA set of registered descriptors, and the wait for their readiness.
Details#
The reactor uses epoll on Linux and kqueue on Darwin. Readiness is level-triggered: a
descriptor that stays readable is reported by each wait. The reactor never reads, writes, or
closes your descriptors. Dropping the reactor closes the kernel queue when no Waker is left.
Gotchas#
Deregister a descriptor before you close it.
On Darwin, readable and writable readiness come as two events with the same token.
Use one reactor on one thread at a time. Other threads use a
Waker.
Implementation Transferable for Reactor#
impl Transferable for ReactorWaker#
pub struct WakerA handle that ends a Reactor.wait from any thread.
Details#
Waker.wake makes the current or the next wait of its reactor return. Many wakes before one
wait end that wait one time. A waker keeps the kernel queue of its reactor open, also after the
reactor is dropped; a wake then has no effect.
Implementation Transferable for Waker#
impl Transferable for WakerImplementation Concurrent for Waker#
impl Concurrent for WakerImplementation Waker#
impl WakerMethod wake#
pub effect fn wake(self: &Self) -> () ! ReactorErrorMakes the current or the next wait of the reactor return.
Errors#
ReactorError with operation Wake if the wake source cannot be signaled.
Method clone#
pub fn clone(self: &Self) -> WakerReturns another waker for the same reactor.
WAKE_TOKEN#
pub const WAKE_TOKEN: u64The token that a Reactor keeps for its wake source. Do not register a descriptor with it.
Implementation Reactor#
impl ReactorAssociated function make#
pub effect fn make() -> Reactor ! ReactorError | OutOfMemoryError ? &mut AllocatorOpens a new kernel queue and wake source, with no registered descriptors.
Details#
The descriptors are close-on-exec, so a child process does not get them.
Errors#
ReactorErrorwith operationCreateif the system cannot open the queue or the wake source, for example when the process has too many open descriptors.OutOfMemoryErrorif the shared state cannot be allocated.
Method waker#
pub fn waker(self: &Self) -> WakerReturns a Waker that ends a wait of this reactor from any thread.
Method register#
pub effect fn register(self: &mut Self, fd: i32, token: u64, interest: Interest) -> () ! ReactorErrorAdds the descriptor fd with token, and reports it when it matches interest.
Details#
The reactor does not take ownership of fd. Each event of fd carries token.
Errors#
ReactorError with operation Register, for example with code 9 (EBADF) if fd is not
open, or 17 (EEXIST) on Linux if fd is already registered. A token equal to
WAKE_TOKEN fails with code 22 (EINVAL).
Method modify#
pub effect fn modify(self: &mut Self, fd: i32, token: u64, interest: Interest) -> () ! ReactorErrorChanges the token and the interest of the registered descriptor fd.
Errors#
ReactorError with operation Modify, for example with code 2 (ENOENT) on Linux if fd
is not registered. A token equal to WAKE_TOKEN fails with code 22 (EINVAL).
Method deregister#
pub effect fn deregister(self: &mut Self, fd: i32) -> () ! ReactorErrorRemoves the descriptor fd. Later waits do not report it.
Errors#
ReactorError with operation Deregister, for example with code 2 (ENOENT) if fd is
not registered.
Method wait#
pub effect fn wait(self: &mut Self, events: &mut [ReadyEvent], timeout: Option<u64>) -> usize ! ReactorErrorWaits until a registered descriptor is ready, a Waker wakes the reactor, or the timeout
passes. Writes the ready descriptors to the start of events, and returns their number.
Details#
timeout is in nanoseconds. none waits with no limit, and some(0) checks once and does
not block. On Linux, a timeout is rounded up to whole milliseconds. A wake ends the wait and
is not written to events, so a wait can return 0 before the timeout. A signal that interrupts
the wait also returns 0. At most events.length events are returned; the others stay for the
next wait.
Errors#
ReactorError with operation Wait if the system call fails for a reason other than a
signal.
Category: Input and output.
Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, x86_64-unknown-linux-gnu.