Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
reactor.c
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#include "serene/rt/reactor.h"
19
20#include <assert.h>
21#include <limits.h>
22
23#include "backend.h"
24#include "internal.h"
25#include "serene/rt/engine.h"
28#include "serene/utils.h"
29
30// A magnitude of 8 corresponds to 256 slot on the ring
31// -----------------------------------------------------------------------------
32// Lifecycle
33// -----------------------------------------------------------------------------
35 PANIC_IF_NULL(engine);
36
37 srn_reactor_t *reactor =
39 PANIC_IF_NULL(reactor);
40
41 // This will allocate a root context which will have its own block of memory
42 reactor->ctx = srn_context_make(engine);
43 PANIC_IF_NULL(reactor->ctx);
44
45 switch (reactor->ctx->engine->config.reactor.backend) {
46#ifdef __linux__
49 break;
50#endif
51 default:
52 PANIC("IO backend is not implemented yet");
53 }
54
55 reactor->backend_state = nullptr;
56 reactor->channels = nullptr;
57 reactor->nchannels = 0;
58 reactor->notify = nullptr;
59
60 atomic_init(&reactor->running, false);
61 atomic_init(&reactor->inflight, 0);
62
63 return reactor;
64}
65
67 // Never activated, or already shut down. Nothing to release.
68 RETURN_IF_NULL(reactor)
69 if (!atomic_load_explicit(&reactor->running, memory_order_acquire)) {
70 return;
71 }
72
73 atomic_store_explicit(&reactor->running, false, memory_order_release);
74 // Break the reactor out of its wait so it observes
75 // `running == false` and exits, rather than blocking until some descriptor
76 // happens to fire.
77 reactor->backend->wake(reactor);
78 srn_thread_join(&reactor->thread);
79 reactor->backend->shutdown(reactor);
80
81 // The reactor thread is joined, so the timer heap is ours now. Free any
82 // timers that never fired (their fibers are reaped by the scheduler), then
83 // the heap.
84 void *out = nullptr;
85 while (pqmh_pop(reactor->ctx, &reactor->timers, &out)) {
86 srn_mm_free(reactor->ctx->engine->mm, out);
87 }
88 pqmh_free(reactor->ctx, &reactor->timers);
89}
90
91// -----------------------------------------------------------------------------
92// Static IO errors
93// -----------------------------------------------------------------------------
94// An IO failure is fully described by its tag (a fixed tag and message), so it
95// need not be allocated per occurrence. These shared, immutable instances let a
96// completion or result point one at `maybe_error` with no allocation and no
97// lifetime concern; they are read only and safe to share across threads. The
98// last entry is the fallback for any tag without a dedicated one.
100 {CONNECTION_REFUSED, (char *)"connection refused"},
101 {CONNECTION_RESET, (char *)"connection reset by peer"},
102 {CONNECTION_ABORTED, (char *)"connection aborted"},
103 {BROKEN_PIPE, (char *)"broken pipe"},
104 {BAD_DESCRIPTOR, (char *)"bad file descriptor"},
105 {TIMED_OUT, (char *)"operation timed out"},
106 {CANCELED, (char *)"operation canceled"},
107 {NOT_CONNECTED, (char *)"socket is not connected"},
108 {ADDRESS_IN_USE, (char *)"address already in use"},
109 {HOST_UNREACHABLE, (char *)"host is unreachable"},
110 {NETWORK_UNREACHABLE, (char *)"network is unreachable"},
111 {MESSAGE_TOO_LONG, (char *)"message too long"},
112 {TOO_MANY_OPEN_FILES, (char *)"too many open files"},
113 {PERMISSION_DENIED, (char *)"permission denied"},
114 {OUT_OF_MEMORY, (char *)"out of memory"},
115 {NOT_FOUND, (char *)"no such file or directory"},
116 {ALREADY_EXISTS, (char *)"file already exists"},
117 {IS_A_DIRECTORY, (char *)"is a directory"},
118 {NOT_A_DIRECTORY, (char *)"not a directory"},
119 {NO_SPACE, (char *)"no space left on device"},
120 {INVALID_ARGUMENT, (char *)"invalid argument"},
121 {CHANNEL_BUSY, (char *)"reactor channel is at capacity"},
122 {UNKNOWN_IO_ERROR, (char *)"unknown IO error"},
123};
124
126
127 size_t count = sizeof(io_errors) / sizeof(io_errors[0]);
128
129 for (size_t i = 0; i < count; i++) {
130 if (io_errors[i].tag == tag) {
131 return &io_errors[i];
132 }
133 }
134 // the UNKNOWN_IO_ERROR fallback
135 return &io_errors[count - 1];
136}
137
138// -----------------------------------------------------------------------------
139// Completions
140// -----------------------------------------------------------------------------
142 srn_reactor_t *reactor, size_t channel, srn_reactor_io_completion_t completion
143) {
144 srn_reactor_io_channel_t *ch = &reactor->channels[channel];
145 // Admission control bounds a channel's in-flight ops at the ring capacity,
146 // and the op being posted is one of them and not yet in the CQ, so the CQ
147 // always has a slot. A full CQ here means the accounting is broken; a spin
148 // in its place would stall the whole reactor behind one busy worker.
149 PANIC_IF(
150 !srn_spsc_ring_push(ch->cq, &completion),
151 "reactor: completion queue full despite admission control; this is a bug"
152 );
153
155 reactor_complete, channel, completion.result, completion.maybe_error != nullptr,
156 completion.fiber_data
157 );
158
159 // The in-flight count is dropped by the consumer through srn_reactor_op_done,
160 // not here. A fiber's waiter must be readied before the op counts as gone, or
161 // quiescence could fire in the gap between posting and readying.
162 if (reactor->notify != nullptr) {
163 reactor->notify(reactor->ctx->engine->scheduler, channel);
164 }
165}
166
167void srn_reactor_op_done(srn_reactor_t *reactor, size_t channel) {
168 PANIC_IF_NULL(reactor);
169 PANIC_IF(channel >= reactor->nchannels, "reactor: channel id out of range");
170 reactor->channels[channel].inflight--;
171 atomic_fetch_sub(&reactor->inflight, 1);
172 REACTOR_TRACEPOINT(reactor_op_done, channel);
173}
174
176 PANIC_IF_NULL(reactor);
177 return atomic_load(&reactor->inflight) == 0;
178}
179
181 srn_reactor_t *reactor, size_t channel, const srn_reactor_io_request_t *request
182) {
183 PANIC_IF_NULL(reactor);
184 PANIC_IF_NULL(request);
185 PANIC_IF(channel >= reactor->nchannels, "reactor: channel id out of range");
186
187 // A clean panic beats what follows otherwise, the backend state is gone
188 // after shutdown, so the wake below would dereference null.
189 PANIC_IF(
190 !atomic_load_explicit(&reactor->running, memory_order_acquire),
191 "reactor: submit on a reactor that is not running"
192 );
193
194 srn_reactor_io_channel_t *ch = &reactor->channels[channel];
195
196 // Admission control. A channel holds at most one ring's worth of ops in
197 // flight, counted from submit until the completion is consumed. The cap is
198 // what makes ring overflow impossible, the SQ holds at most the admitted
199 // ops, and the CQ holds at most the admitted ops minus the one being
200 // posted, so neither the push below nor the post on the reactor thread can
201 // find its ring full. A rejected submit is failed back to the caller as a
202 // transient condition; it clears as the worker consumes completions.
203 if (ch->inflight >= reactor->ring_capacity) {
204 REACTOR_TRACEPOINT(reactor_submit_rejected, channel, ch->inflight);
205 return false;
206 }
207 ch->inflight++;
208
209 // Count the op as in flight BEFORE the request becomes visible to the reactor
210 // thread. If the bump came after the push, the reactor could complete the op
211 // and the consumer could call srn_reactor_op_done before the bump ran --
212 // underflowing the counter and breaking quiescence.
213 atomic_fetch_add(&reactor->inflight, 1);
214
215 // The SQ is single-producer, the worker that owns this channel.
216 PANIC_IF(
217 !srn_spsc_ring_push(ch->sq, request),
218 "reactor: submission queue full despite admission control; this is a bug"
219 );
220
221 REACTOR_TRACEPOINT(reactor_submit, channel, (int)request->op, request->fd, request->fiber_data);
222
223 // Rouse the reactor so it drains the new request at once instead of waiting
224 // out its idle timeout.
225 reactor->backend->wake(reactor);
226 return true;
227}
228
230 srn_reactor_t *reactor, size_t channel, srn_reactor_io_completion_t *out, size_t max
231) {
232 PANIC_IF_NULL(reactor);
233 PANIC_IF_NULL(out);
234 PANIC_IF(channel >= reactor->nchannels, "reactor: channel id out of range");
235
236 srn_reactor_io_channel_t *ch = &reactor->channels[channel];
237 size_t n = 0;
238
239 while (n < max && (int)srn_spsc_ring_pop(ch->cq, &out[n])) {
240 n++;
241 }
242
243 return n;
244}
245
247 PANIC_IF_NULL(reactor);
248 PANIC_IF(channel >= reactor->nchannels, "reactor: channel id out of range");
249 return (!srn_spsc_ring_empty(reactor->channels[channel].cq)) != 0;
250}
251
252// -----------------------------------------------------------------------------
253// The reactor thread
254// -----------------------------------------------------------------------------
255// Timers
256// -----------------------------------------------------------------------------
257// A min-heap of pending timers keyed by absolute monotonic deadline. The heap
258// stores pointers, so each entry is its own srn_mm_malloc allocation, owned by
259// the reactor and freed when the timer fires (or at shutdown). Touched only on
260// the reactor thread, so no locking.
261typedef struct srn_timer_t {
262 /// Absolute CLOCK_MONOTONIC deadline
263 uint64_t deadline_ns;
264 /// Channel the completion is posted to
265 size_t channel;
266 /// Round tripped to the waiting fiber
269
270/**
271 * Order timers by deadline so the soonest sits at the heap root.
272 */
273static bool timer_less(srn_context_t *ctx, const void *a, const void *b) {
274 UNUSED(ctx);
275 return ((const srn_timer_t *)a)->deadline_ns < ((const srn_timer_t *)b)->deadline_ns;
276}
277
278static const pqmh_control_t timer_ctl = {.less = timer_less};
279
280/**
281 * Register a SLEEP: allocate a timer entry and push it onto the heap.
282 */
283static void timer_add(srn_reactor_t *r, size_t channel, const srn_reactor_io_request_t *req) {
284 assert(r != nullptr);
285
286 srn_timer_t *t = srn_mm_malloc(r->ctx->engine->mm, sizeof(srn_timer_t));
287 PANIC_IF_NULL(t);
288
289 t->deadline_ns = req->deadline_ns;
290 t->channel = channel;
291 t->fiber_data = req->fiber_data;
292 REACTOR_TRACEPOINT(reactor_timer_armed, channel, req->deadline_ns);
293 pqmh_push(r->ctx, &r->timers, t);
294}
295
296/**
297 * Milliseconds until the soonest deadline (rounded up), clamped to a valid
298 * `epoll_wait` timeout, 0 if a timer is already due, INT_MAX for a far-off
299 * deadline, and -1 (block indefinitely) when no timers are pending.
300 */
302 assert(r != nullptr);
303
304 const srn_timer_t *t = pqmh_peek(&r->timers);
305
306 if (t == nullptr) {
307 return -1;
308 }
309
310 uint64_t now = srn_now_ns();
311 if (t->deadline_ns <= now) {
312 return 0;
313 }
314
315 // Convert the ns remaining to the ms epoll_wait timeout, rounding UP (add
316 // NS_PER_MS-1) so a sub-millisecond remainder waits 1ms rather than 0 -- we
317 // must never wake early and busy-spin firing nothing.
318 uint64_t ms = ((t->deadline_ns - now) + (SRN_NS_PER_MS - 1)) / SRN_NS_PER_MS;
319 return (ms > (uint64_t)INT_MAX) ? INT_MAX : (int)ms;
320}
321
322/**
323 * Fire every timer whose deadline has passed, pop it (in deadline order), post
324 * a zero result completion to its channel, and free the entry.
325 */
327 assert(r != nullptr);
328
329 uint64_t now = srn_now_ns();
330 const srn_timer_t *top;
331
332 while ((top = pqmh_peek(&r->timers)) != nullptr && top->deadline_ns <= now) {
333 void *out = nullptr;
334 UNUSED(pqmh_pop(r->ctx, &r->timers, &out));
335
336 srn_timer_t *t = (srn_timer_t *)out;
337 REACTOR_TRACEPOINT(reactor_timer_fired, t->channel, t->deadline_ns);
338
340 r, t->channel, (srn_reactor_io_completion_t){.fiber_data = t->fiber_data, .result = 0}
341 );
342 srn_mm_free(r->ctx->engine->mm, t);
343 }
344}
345
346// -----------------------------------------------------------------------------
347static void process_request(srn_reactor_t *r, size_t channel, const srn_reactor_io_request_t *req) {
348 switch (req->op) {
351 r, channel, (srn_reactor_io_completion_t){.fiber_data = req->fiber_data, .result = 0}
352 );
353 break;
363 // Descriptor ops go to the backend, it arms the fd and posts the completion
364 // when it is ready. The backend reports its own failures through a
365 // completion, so there is nothing to check here.
366 r->backend->submit(r, channel, req);
367 break;
368
370 timer_add(r, channel, req);
371 break;
372
374 PANIC("reactor: CANCEL is not implemented yet");
375 }
376}
377
380 // TODO(lxsameer): Should we make this configurable via a policy or something?
381 // For example, pop from each channel once vs pop all the SQEs per channel
382 for (size_t c = 0; c < r->nchannels; c++) {
383 while (srn_spsc_ring_pop(r->channels[c].sq, &req)) {
384 process_request(r, c, &req);
385 }
386 }
387}
388
389static void reactor_thread_main(void *arg) {
390 srn_reactor_t *r = (srn_reactor_t *)arg;
391 while (atomic_load_explicit(&r->running, memory_order_acquire)) {
393 // Wait until the soonest timer deadline (or indefinitely if none), unless a
394 // kernel event, a submission nudge, or shutdown breaks us out sooner. Then
395 // fire whatever timers came due.
398 }
399}
400
401void srn_reactor_activate(srn_reactor_t *reactor, size_t nchannels, srn_reactor_notify_fn notify) {
402
403 PANIC_IF_NULL(reactor);
404 PANIC_IF(nchannels == 0, "reactor: at least one channel is required");
405
406 reactor->notify = notify;
407 reactor->nchannels = nchannels;
408
409 // The ring size is a runtime knob; both rings of every channel use it.
410 // Validate the knobs once here, so everything sized from them downstream
411 // (rings, the backend op pool, the reap stack buffers) is sound, a zero
412 // batch is a zero length VLA, and an oversized magnitude wraps the pool
413 // arithmetic.
414 const size_t ring_mag = reactor->ctx->engine->config.reactor.ring_magnitude;
415 PANIC_IF(ring_mag < 1 || ring_mag > 16, "reactor: ring_magnitude must be within 1..16");
416 reactor->ring_capacity = (size_t)1 << ring_mag;
417
418 const size_t batch = reactor->ctx->engine->config.reactor.reap_batch;
419 PANIC_IF(
420 batch < 1 || batch > reactor->ring_capacity,
421 "reactor: reap_batch must be within 1..ring capacity"
422 );
423
424 reactor->channels = ALLOCN(reactor->ctx, srn_reactor_io_channel_t, nchannels);
425 PANIC_IF_NULL(reactor->channels);
426
427 for (size_t c = 0; c < nchannels; c++) {
428 reactor->channels[c].inflight = 0;
429 // Setup the SQ of the channel
430 reactor->channels[c].sq =
431 srn_spsc_ring_make(reactor->ctx, ring_mag, sizeof(srn_reactor_io_request_t));
432 PANIC_IF_NULL(reactor->channels[c].sq);
433 PANIC_IF(HAS_ERROR(*reactor->channels[c].sq), reactor->channels[c].sq->maybe_error->msg);
434
435 // Setup the CQ of the channel
436 reactor->channels[c].cq =
437 srn_spsc_ring_make(reactor->ctx, ring_mag, sizeof(srn_reactor_io_completion_t));
438 PANIC_IF_NULL(reactor->channels[c].cq);
439 PANIC_IF(HAS_ERROR(*reactor->channels[c].cq), reactor->channels[c].cq->maybe_error->msg);
440 }
441
442 // The timer heap is reactor thread private. Create it before the thread
443 // spawns.
444 reactor->timers = pqmh_make(reactor->ctx, &timer_ctl, 0);
445
446 // Bring the backend up now that nchannels is known, its op pool is sized from
447 // the channel count, so it cannot be initialized before activation.
448 srn_error_t *err = reactor->backend->init(reactor);
449 PANIC_IF(err != nullptr, err->msg);
450
451 atomic_store_explicit(&reactor->running, true, memory_order_release);
452 PANIC_IF(
454 "reactor: failed to spawn the reactor thread"
455 );
456}
int n
Definition acutest.h:525
Internal reactor backend interface.
const srn_reactor_backend_t srn_reactor_backend_epoll
@ SRN_REACTOR_EPOLL
srn_context_t * srn_context_make(srn_engine_t *engine)
Make an empty context, by allocating a new memory block.
Definition context.c:39
#define ALLOCN(ctx, T, N)
Definition context.h:85
void * srn_mm_immortal_allocate_aligned(srn_mm_t *mm, size_t size, size_t alignment)
Allocate memory on the importal block which will never gets freed.
Definition default.c:426
void srn_mm_free(srn_mm_t *mm, void *ptr)
Release a pointer previously returned by srn_mm_malloc or srn_mm_reallocate.
Definition default.c:169
void * srn_mm_malloc(srn_mm_t *mm, size_t size)
Generic allocations that do not participate in the block based pools.
Definition default.c:155
#define HAS_ERROR(x)
True when x – a value carrying an ERROR_HEADER – has an error attached.
Definition errors.h:63
srn_error_tag_t
The kind of a failure.
Definition errors.h:75
@ BROKEN_PIPE
Definition errors.h:94
@ HOST_UNREACHABLE
Definition errors.h:100
@ NETWORK_UNREACHABLE
Definition errors.h:101
@ IS_A_DIRECTORY
Definition errors.h:108
@ INVALID_ARGUMENT
The request itself is malformed (an empty POLL interest mask, for example), so retrying it unchanged ...
Definition errors.h:113
@ CHANNEL_BUSY
The channel already has a full ring's worth of operations in flight.
Definition errors.h:117
@ PERMISSION_DENIED
Definition errors.h:104
@ CONNECTION_RESET
Definition errors.h:92
@ UNKNOWN_IO_ERROR
Definition errors.h:118
@ TOO_MANY_OPEN_FILES
Definition errors.h:103
@ NOT_CONNECTED
Definition errors.h:98
@ CONNECTION_REFUSED
Definition errors.h:91
@ CONNECTION_ABORTED
Definition errors.h:93
@ ADDRESS_IN_USE
Definition errors.h:99
@ BAD_DESCRIPTOR
Definition errors.h:95
@ OUT_OF_MEMORY
Definition errors.h:105
@ NOT_FOUND
Definition errors.h:106
@ CANCELED
Definition errors.h:97
@ NOT_A_DIRECTORY
Definition errors.h:109
@ TIMED_OUT
Definition errors.h:96
@ MESSAGE_TOO_LONG
Definition errors.h:102
@ ALREADY_EXISTS
Definition errors.h:107
@ NO_SPACE
Definition errors.h:110
void pqmh_free(srn_context_t *ctx, pqmh_t *pq)
Release the backing array and reset the queue to empty.
Definition pqmh.c:142
void pqmh_push(srn_context_t *ctx, pqmh_t *pq, void *elem)
Insert elem, growing the backing array (doubling) if it is full.
Definition pqmh.c:92
pqmh_t pqmh_make(srn_context_t *ctx, const pqmh_control_t *ctl, size_t initial_cap)
Create an empty queue ordered by ctl, with room for initial_cap elements (a small default is used whe...
Definition pqmh.c:81
void * pqmh_peek(const pqmh_t *pq)
The highest priority element without removing it, or NULL when the queue is empty.
Definition pqmh.c:132
bool pqmh_pop(srn_context_t *ctx, pqmh_t *pq, void **out)
Remove the highest priority element into *out and return true, or return false (leaving *out untouche...
Definition pqmh.c:111
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:401
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:167
static void drain_submissions(srn_reactor_t *r)
Definition reactor.c:378
srn_error_t * srn_reactor_errors_static(srn_error_tag_t tag)
Return the shared, immutable error for an IO failure tag.
Definition reactor.c:125
static int timer_next_timeout(srn_reactor_t *r)
Milliseconds until the soonest deadline (rounded up), clamped to a valid epoll_wait timeout,...
Definition reactor.c:301
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:229
static srn_error_t io_errors[]
Definition reactor.c:99
static bool timer_less(srn_context_t *ctx, const void *a, const void *b)
Order timers by deadline so the soonest sits at the heap root.
Definition reactor.c:273
static const pqmh_control_t timer_ctl
Definition reactor.c:278
bool srn_reactor_idle(srn_reactor_t *reactor)
Whether the reactor has no operations in flight.
Definition reactor.c:175
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
static void process_request(srn_reactor_t *r, size_t channel, const srn_reactor_io_request_t *req)
Definition reactor.c:347
static void timer_fire_expired(srn_reactor_t *r)
Fire every timer whose deadline has passed, pop it (in deadline order), post a zero result completion...
Definition reactor.c:326
static void timer_add(srn_reactor_t *r, size_t channel, const srn_reactor_io_request_t *req)
Register a SLEEP: allocate a timer entry and push it onto the heap.
Definition reactor.c:283
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:246
static void reactor_thread_main(void *arg)
Definition reactor.c:389
void srn_reactor_post_completion(srn_reactor_t *reactor, size_t channel, srn_reactor_io_completion_t completion)
Post a finished operation to channel's completion queue and rouse the channel's worker through the no...
Definition reactor.c:141
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:180
Reactor overview.
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:180
@ SRN_REACTOR_IO_SLEEP
Wait out a deadline.
Definition reactor.h:69
@ SRN_REACTOR_IO_ACCEPT
Accept a pending connection on the listening socket fd, with flags (the accept4 flags).
Definition reactor.h:82
@ SRN_REACTOR_IO_READ
Read from a descriptor into buf. Uses fd, buf, len, offset.
Definition reactor.h:71
@ SRN_REACTOR_IO_SENDTO
Send buf/len on fd to the address in addr/addrlen with flags.
Definition reactor.h:95
@ SRN_REACTOR_IO_NOP
Completes immediately with a zero result.
Definition reactor.h:67
@ SRN_REACTOR_IO_CONNECT
Connect fd to the address in addr/addrlen.
Definition reactor.h:86
@ SRN_REACTOR_IO_CANCEL
Cancel one inflight operation, named by its fiber_data.
Definition reactor.h:77
@ SRN_REACTOR_IO_RECVFROM
Receive a datagram from fd into buf/len with flags.
Definition reactor.h:91
@ 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:99
@ SRN_REACTOR_IO_WRITE
Write buf to a descriptor. Uses fd, buf, len, offset.
Definition reactor.h:73
@ SRN_REACTOR_IO_POLL
Wait until fd is ready for the events requested in flags (EPOLLIN/EPOLLOUT) without performing any tr...
Definition reactor.h:108
@ SRN_REACTOR_IO_SENDMSG
Send the struct msghdr that buf points at, on fd, with flags.
Definition reactor.h:103
#define REACTOR_TRACEPOINT(...)
Definition reactor.h:58
bool srn_spsc_ring_push(srn_spsc_ring_t *r, const void *elem)
Producer side.
Definition spsc_ring.c:61
bool srn_spsc_ring_empty(const srn_spsc_ring_t *r)
Whether the ring currently holds no elements.
Definition spsc_ring.c:103
srn_spsc_ring_t * srn_spsc_ring_make(srn_context_t *ctx, size_t magnitude, size_t elem_size)
Set up a ring by allocating the buf to be the size of cap * elem_size.
Definition spsc_ring.c:27
bool srn_spsc_ring_pop(srn_spsc_ring_t *r, void *out)
Consumer side.
Definition spsc_ring.c:83
A mutable single producer, single consumer ring of fixed size elements.
Per type configuration.
Definition pqmh.h:57
srn_reactor_config_t reactor
srn_engine_t * engine
Long term state of the compiler.
Definition context.h:49
Engine is a structure to own the long living and main pieces of the compiler.
Definition engine.h:51
srn_configuration_t config
The runtime's tunable knobs, the single source for every configurable value (see configuration....
Definition engine.h:62
srn_scheduler_t * scheduler
The fiber scheduler, that is the entry point of the fiber subsystem.
Definition engine.h:75
srn_mm_t * mm
Memory manager.
Definition engine.h:65
A runtime error, a tag classifying the failure and a human-readable message.
Definition errors.h:125
char * msg
Definition errors.h:127
void(* shutdown)(srn_reactor_t *reactor)
Clean up after the backend.
Definition backend.h:67
void(* wake)(srn_reactor_t *reactor)
Definition backend.h:77
void(* submit)(srn_reactor_t *reactor, size_t channel, const srn_reactor_io_request_t *req)
Submit a new request to the backend.
Definition backend.h:71
srn_error_t *(* init)(srn_reactor_t *reactor)
Initialize the backend in the given reactor.
Definition backend.h:64
void(* wait)(srn_reactor_t *reactor, int timeout_ms)
Wait for readiness and complete any ready operations.
Definition backend.h:75
size_t reap_batch
Most completions a worker drains from its channel in a single pass.
size_t ring_magnitude
Magnitude of each channel's SQ/CQ rings, capacity is 1 << magnitude.
srn_reactor_backend_e backend
What IO backend to use.
A channel, the per-worker submission and completion ring pair.
Definition internal.h:30
srn_spsc_ring_t * cq
Definition internal.h:34
size_t inflight
Operations submitted on this channel and not yet consumed.
Definition internal.h:40
srn_spsc_ring_t * sq
Definition internal.h:32
A completion, the result of one submission, echoed back on the channel.
Definition reactor.h:159
void * fiber_data
The fiber_data of the submission this completes.
Definition reactor.h:162
size_t result
The result of the operation on success, bytes transferred for the transfers, the accepted descriptor ...
Definition reactor.h:166
A submission, one request a fiber places on its channel.
Definition reactor.h:116
int fd
The descriptor the operation targets.
Definition reactor.h:120
srn_reactor_io_op_t op
Definition reactor.h:117
uint64_t deadline_ns
The wake deadline for SRN_REACTOR_IO_SLEEP, on the monotonic clock, in nanoseconds.
Definition reactor.h:131
void * fiber_data
Opaque token, round tripped to the matching completion.
Definition reactor.h:136
srn_thread_t thread
The reactor thread and its run flag.
Definition internal.h:65
pqmh_t timers
Pending timers (like SLEEP, and IO deadlines) as a min-heap keyed by absolute monotonic deadline.
Definition internal.h:77
srn_reactor_io_channel_t * channels
One channel per worker, there is a 1to1 mapping between worker ids and channel ids.
Definition internal.h:56
srn_reactor_notify_fn notify
Wakes the worker that owns a channel a completion landed on.
Definition internal.h:52
srn_context_t * ctx
Definition internal.h:44
size_t ring_capacity
Capacity of every ring (1 << ring_magnitude), fixed at activation.
Definition internal.h:61
const srn_reactor_backend_t * backend
The chosen kernel mechanism and its private state, set at activation.
Definition internal.h:47
size_t nchannels
Definition internal.h:57
_Atomic bool running
Definition internal.h:66
void * backend_state
Definition internal.h:48
_Atomic size_t inflight
Operations submitted but not yet consumed, across all channels.
Definition internal.h:71
void * fiber_data
Round tripped to the waiting fiber.
Definition reactor.c:267
size_t channel
Channel the completion is posted to.
Definition reactor.c:265
uint64_t deadline_ns
Absolute CLOCK_MONOTONIC deadline.
Definition reactor.c:263
srn_thread_status_t srn_thread_join(srn_thread_t *t)
Block until the thread started for t returns.
@ SRN_THREAD_OK
Definition thread.h:62
srn_thread_status_t srn_thread_spawn(srn_thread_t *t, void(*fn)(void *), void *arg)
Run fn(arg) on a new OS thread.
#define PANIC_IF_NULL(ptr)
Definition utils.h:66
#define SRN_NS_PER_MS
nanoseconds in one millisecond
Definition utils.h:308
uint64_t srn_now_ns(void)
The current time on the monotonic clock, in nanoseconds.
Definition utils.c:46
#define RETURN_IF_NULL(ptr)
Definition utils.h:67
#define PANIC_IF(cond, msg)
Definition utils.h:59
#define UNUSED(x)
Definition utils.h:45
#define PANIC(msg)
Definition utils.h:53