Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
reactor.h
Go to the documentation of this file.
1/* -*- C -*-
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <[email protected]>
4 *
5 * This library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19/** @file
20 Reactor overview
21
22 The reactor is in charge of all the IO in the runtime.
23
24 It is completion based and modeled after
25 [io_uring](https://man7.org/linux/man-pages/man7/io_uring.7.html); a submission
26 describes a whole request, and a completion reports its result. The reactor
27 utilizes different backends for different platforms.
28 A readiness backend such as epoll performs the transfer itself when the
29 descriptor signals readiness and posts the finished result, so the contract is
30 the common subset every platform can honour.
31
32 A worker and the reactor talk through a channel: a channel is a pair of
33 submission queue (SQ) and a completion queue (CQ), a SQ carrying
34 requests toward the reactor and a CQ carrying results back. There
35 is one channel per worker. The reactor owns every channel; a worker holds only
36 the channel id and never touches a raw queue.
37
38 Each request carries `fiber_data`, an opaque token the reactor round-trips
39 untouched: it rides in on the submission and comes back out on the completion,
40 and is how a finished result finds the fiber that was waiting.
41*/
42
43#pragma once
44
45#include <stddef.h>
46#include <stdint.h>
47
48#include "serene/rt/errors.h"
49
50typedef struct srn_engine_t srn_engine_t;
52
53/// The implementation is internal to the runtime
54typedef struct srn_reactor_t srn_reactor_t;
55typedef struct srn_scheduler_t srn_scheduler_t;
56
57/**
58 * The operation a submission asks the reactor to perform.
59 */
60typedef enum srn_reactor_io_op_t {
61 /// Completes immediately with a zero result. Exercises the
62 /// submit-and-complete
63 /// path on its own, with no descriptor and no kernel mechanism.
65 /// Wait out a deadline.
67 /// Read from a descriptor into `buf`. Uses `fd`, `buf`, `len`, `offset`.
69 /// Write `buf` to a descriptor. Uses `fd`, `buf`, `len`, `offset`.
71 /// Cancel one inflight operation, named by its `fiber_data`. Not
72 /// implemented, submitting it panics. It lands together with the fd
73 /// deregistration work.
75 /// Accept a pending connection on the listening socket `fd`, with `flags`
76 /// (the `accept4` flags). The peer address is written to `addr`/`addrlen`
77 /// when `addr` is non-null. The completion's `result` is the accepted
78 /// descriptor; failures arrive through the completion's `maybe_error`.
80 /// Connect `fd` to the address in `addr`/`addrlen`. The connect is started at
81 /// submit time and completes once the socket is writable. `result` is 0 on
82 /// success; failures arrive through the completion's `maybe_error`.
84 /// Receive a datagram from `fd` into `buf`/`len` with `flags`. The sender
85 /// address is written to `addr`/`addrlen` when `addr` is non-null. `result`
86 /// is bytes received; failures arrive through the completion's
87 /// `maybe_error`.
89 /// Send `buf`/`len` on `fd` to the address in `addr`/`addrlen` with `flags`.
90 /// `result` is bytes sent; failures arrive through the completion's
91 /// `maybe_error`.
93 /// Receive into the `struct msghdr` that `buf` points at, on `fd`, with
94 /// `flags` (scatter/gather and ancillary data). `result` is bytes received;
95 /// failures arrive through the completion's `maybe_error`.
97 /// Send the `struct msghdr` that `buf` points at, on `fd`, with `flags`.
98 /// `result` is bytes sent; failures arrive through the completion's
99 /// `maybe_error`.
101 /// Wait until `fd` is ready for the events requested in `flags`
102 /// (`EPOLLIN`/`EPOLLOUT`) without performing any transfer. `result` is the
103 /// ready-events bitmask. The gateway for timerfd, signalfd, eventfd, inotify
104 /// and pidfd.
107
108/**
109 * A submission, one request a fiber places on its channel. The reactor copies
110 * it into the submission queue, so the struct itself need not outlive the call;
111 * a buffer it points at (`buf`) must stay valid until the operation completes.
112 */
115 /// The descriptor the operation targets. Unused by SRN_REACTOR_IO_NOP and
116 /// SRN_REACTOR_IO_SLEEP.
117 int fd;
118 /// The transfer buffer for READ, WRITE, RECVFROM, and SENDTO, and the
119 /// `struct msghdr *` for RECVMSG and SENDMSG.
120 void *buf;
121 /// The transfer length, in bytes, for READ, WRITE, RECVFROM, and SENDTO.
122 size_t len;
123 /// The file offset for SRN_REACTOR_IO_READ and SRN_REACTOR_IO_WRITE; -1 reads
124 /// at the current position.
125 int64_t offset;
126 /// The wake deadline for SRN_REACTOR_IO_SLEEP, on the monotonic clock, in
127 /// nanoseconds.
128 uint64_t deadline_ns;
129 /// Opaque token, round tripped to the matching completion. The reactor never
130 /// reads it. For SRN_REACTOR_IO_CANCEL it names the target operation. The
131 /// workers or the scheduler set their data here and expect it back on the
132 /// completion counterpart
134 /// Socket address. Input for SRN_REACTOR_IO_CONNECT and SRN_REACTOR_IO_SENDTO
135 /// (the peer to use); output for SRN_REACTOR_IO_ACCEPT and
136 /// SRN_REACTOR_IO_RECVFROM (the peer that connected or sent), or null to
137 /// ignore. A `struct sockaddr *`, kept as `void *` so this header stays free
138 /// of socket headers.
139 void *addr;
140 /// Length of `addr`, the exact address length for SRN_REACTOR_IO_CONNECT and
141 /// SRN_REACTOR_IO_SENDTO, the buffer capacity for SRN_REACTOR_IO_ACCEPT and
142 /// SRN_REACTOR_IO_RECVFROM.
143 uint32_t addrlen;
144 /// Operation flags, the `MSG_*` flags for the datagram and message ops, the
145 /// `accept4` flags for SRN_REACTOR_IO_ACCEPT, and the requested readiness
146 /// (`EPOLLIN`/`EPOLLOUT`) for SRN_REACTOR_IO_POLL.
147 int flags;
149
150/**
151 * A completion, the result of one submission, echoed back on the channel. It is a fallible value
152 * (see errors.h). `maybe_error` is null on success and points at the failure otherwise, so
153 * `result`/`addrlen` are meaningful only when there is no error. The backend sets `maybe_error`
154 * from a static IO error to avoid unnecessary memory allocation.
155 */
158 /// The `fiber_data` of the submission this completes.
160 /// The result of the operation on success, bytes transferred for the
161 /// transfers, the accepted descriptor for SRN_REACTOR_IO_ACCEPT, 0 for SRN_REACTOR_IO_CONNECT and
162 /// SRN_REACTOR_IO_NOP. Unspecified when `maybe_error` is set.
163 size_t result;
164 /// For SRN_REACTOR_IO_ACCEPT and SRN_REACTOR_IO_RECVFROM, the actual length of
165 /// the address written into the request's `addr` (the kernel's value-result
166 /// `addrlen`), which lets a caller interpret a variable or mixed-family
167 /// address (IPv6, Unix-domain, a dual-stack listener). The consumer knows its
168 /// op, so it reads this only for those two; unused otherwise.
169 uint32_t addrlen;
171
172/**
173 * The interface that the scheduler supplies so the reactor can announce that a channel has
174 * completions, without the reactor knowing about workers or parking. The reactor calls it after
175 * placing completions on `channel`; the scheduler wakes that channel's worker if it is parked.
176 */
177typedef void (*srn_reactor_notify_fn)(srn_scheduler_t *sched, size_t channel);
178
179// -----------------------------------------------------------------------------
180// Lifecycle
181// -----------------------------------------------------------------------------
182/// Allocate the IO reactor from the engine.
183[[nodiscard]] [[gnu::nonnull(1)]]
185
186/**
187 * Bring the reactor up, allocate `nchannels` channels (one per worker) and
188 * start the reactor thread. `notify` is called with the scheduler and the
189 * channel id whenever a channel gains completions.
190 */
191[[gnu::nonnull(1)]]
192void srn_reactor_activate(srn_reactor_t *reactor, size_t nchannels, srn_reactor_notify_fn notify);
193
194/**
195 * Tear the reactor down, stop and join the reactor thread and release its
196 * channels.
197 */
198[[gnu::nonnull(1)]]
200
201// -----------------------------------------------------------------------------
202// Submit and consume
203// -----------------------------------------------------------------------------
204/**
205 * Place a request on `channel`'s submission queue and rouse the reactor.
206 * Returns false without submitting when the channel already has a full
207 * ring's worth of operations in flight. The condition is transient, it
208 * clears as the channel's worker consumes completions, so the caller can
209 * fail the operation back with CHANNEL_BUSY and let the fiber retry.
210 */
211[[nodiscard]] [[gnu::nonnull(1, 3)]]
213 srn_reactor_t *reactor, size_t channel, const srn_reactor_io_request_t *request
214);
215
216/**
217 * Drain up to `max` completions from `channel`'s completion queue into `out`,
218 * returning how many were taken.
219 */
220[[gnu::nonnull(1, 3)]]
221size_t srn_reactor_reap(
222 srn_reactor_t *reactor, size_t channel, srn_reactor_io_completion_t *out, size_t max
223);
224
225/**
226 * Drop `channel`'s and the reactor's in-flight counts by one. The consumer of
227 * a completion calls this after it has finished acting on it (for fibers,
228 * after the waiting fiber is readied), so that "in flight" reaches zero only
229 * once every result has been both produced and handled. Dropping the channel
230 * count is also what re-opens the channel's admission window (see
231 * srn_reactor_submit).
232 */
233[[gnu::nonnull(1)]]
234void srn_reactor_op_done(srn_reactor_t *reactor, size_t channel);
235
236/**
237 * Whether the reactor has no operations in flight. The scheduler folds this
238 * into quiescence, a pool with parked workers and an empty run queue is only
239 * truly done when the reactor is idle too.
240 */
241[[nodiscard]] [[gnu::nonnull(1)]]
242bool srn_reactor_idle(srn_reactor_t *reactor);
243
244/**
245 * Drain and act on `channel`'s completions. Called by the worker that owns the
246 * channel, from its loop. Per completion it sets the waiter's result, readies
247 * the fiber, and drops the in-flight count.
248 */
249[[gnu::nonnull(1)]]
250void srn_reactor_consume(srn_reactor_t *reactor, size_t channel);
251
252/**
253 * Whether `channel`'s completion queue has unconsumed completions. The worker
254 * checks this under the scheduler lock before parking, so a completion landing
255 * just before it sleeps is not missed.
256 */
257[[nodiscard]] [[gnu::nonnull(1)]]
258bool srn_reactor_channel_has_completions(srn_reactor_t *reactor, size_t channel);
Error handling for the runtime.
void srn_reactor_activate(srn_reactor_t *reactor, size_t nchannels, srn_reactor_notify_fn notify)
Bring the reactor up, allocate nchannels channels (one per worker) and start the reactor thread.
Definition reactor.c:390
void srn_reactor_consume(srn_reactor_t *reactor, size_t channel)
Drain and act on channel's completions.
Definition io.c:121
void srn_reactor_op_done(srn_reactor_t *reactor, size_t channel)
Drop channel's and the reactor's in-flight counts by one.
Definition reactor.c:162
void(* srn_reactor_notify_fn)(srn_scheduler_t *sched, size_t channel)
The interface that the scheduler supplies so the reactor can announce that a channel has completions,...
Definition reactor.h:177
srn_reactor_io_op_t
The operation a submission asks the reactor to perform.
Definition reactor.h:60
@ SRN_REACTOR_IO_SLEEP
Wait out a deadline.
Definition reactor.h:66
@ SRN_REACTOR_IO_ACCEPT
Accept a pending connection on the listening socket fd, with flags (the accept4 flags).
Definition reactor.h:79
@ SRN_REACTOR_IO_READ
Read from a descriptor into buf. Uses fd, buf, len, offset.
Definition reactor.h:68
@ SRN_REACTOR_IO_SENDTO
Send buf/len on fd to the address in addr/addrlen with flags.
Definition reactor.h:92
@ SRN_REACTOR_IO_NOP
Completes immediately with a zero result.
Definition reactor.h:64
@ SRN_REACTOR_IO_CONNECT
Connect fd to the address in addr/addrlen.
Definition reactor.h:83
@ SRN_REACTOR_IO_CANCEL
Cancel one inflight operation, named by its fiber_data.
Definition reactor.h:74
@ SRN_REACTOR_IO_RECVFROM
Receive a datagram from fd into buf/len with flags.
Definition reactor.h:88
@ SRN_REACTOR_IO_RECVMSG
Receive into the struct msghdr that buf points at, on fd, with flags (scatter/gather and ancillary da...
Definition reactor.h:96
@ SRN_REACTOR_IO_WRITE
Write buf to a descriptor. Uses fd, buf, len, offset.
Definition reactor.h:70
@ SRN_REACTOR_IO_POLL
Wait until fd is ready for the events requested in flags (EPOLLIN/EPOLLOUT) without performing any tr...
Definition reactor.h:105
@ SRN_REACTOR_IO_SENDMSG
Send the struct msghdr that buf points at, on fd, with flags.
Definition reactor.h:100
size_t srn_reactor_reap(srn_reactor_t *reactor, size_t channel, srn_reactor_io_completion_t *out, size_t max)
Drain up to max completions from channel's completion queue into out, returning how many were taken.
Definition reactor.c:220
bool srn_reactor_idle(srn_reactor_t *reactor)
Whether the reactor has no operations in flight.
Definition reactor.c:169
void srn_reactor_shutdown(srn_reactor_t *reactor)
Tear the reactor down, stop and join the reactor thread and release its channels.
Definition reactor.c:66
srn_reactor_t * srn_reactor_init(srn_engine_t *engine)
Allocate the IO reactor from the engine.
Definition reactor.c:34
bool srn_reactor_channel_has_completions(srn_reactor_t *reactor, size_t channel)
Whether channel's completion queue has unconsumed completions.
Definition reactor.c:237
bool srn_reactor_submit(srn_reactor_t *reactor, size_t channel, const srn_reactor_io_request_t *request)
Place a request on channel's submission queue and rouse the reactor.
Definition reactor.c:174
Engine is a structure to own the long living and main pieces of the compiler.
Definition engine.h:51
One async system behind the reactor contract.
Definition backend.h:55
A completion, the result of one submission, echoed back on the channel.
Definition reactor.h:156
void * fiber_data
The fiber_data of the submission this completes.
Definition reactor.h:159
uint32_t addrlen
For SRN_REACTOR_IO_ACCEPT and SRN_REACTOR_IO_RECVFROM, the actual length of the address written into ...
Definition reactor.h:169
size_t result
The result of the operation on success, bytes transferred for the transfers, the accepted descriptor ...
Definition reactor.h:163
A submission, one request a fiber places on its channel.
Definition reactor.h:113
int fd
The descriptor the operation targets.
Definition reactor.h:117
int flags
Operation flags, the MSG_* flags for the datagram and message ops, the accept4 flags for SRN_REACTOR_...
Definition reactor.h:147
void * addr
Socket address.
Definition reactor.h:139
srn_reactor_io_op_t op
Definition reactor.h:114
void * buf
The transfer buffer for READ, WRITE, RECVFROM, and SENDTO, and the struct msghdr * for RECVMSG and SE...
Definition reactor.h:120
size_t len
The transfer length, in bytes, for READ, WRITE, RECVFROM, and SENDTO.
Definition reactor.h:122
uint64_t deadline_ns
The wake deadline for SRN_REACTOR_IO_SLEEP, on the monotonic clock, in nanoseconds.
Definition reactor.h:128
int64_t offset
The file offset for SRN_REACTOR_IO_READ and SRN_REACTOR_IO_WRITE; -1 reads at the current position.
Definition reactor.h:125
void * fiber_data
Opaque token, round tripped to the matching completion.
Definition reactor.h:133
uint32_t addrlen
Length of addr, the exact address length for SRN_REACTOR_IO_CONNECT and SRN_REACTOR_IO_SENDTO,...
Definition reactor.h:143