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
154 // The in-flight count is dropped by the consumer through srn_reactor_op_done,
155 // not here. A fiber's waiter must be readied before the op counts as gone, or
156 // quiescence could fire in the gap between posting and readying.
157 if (reactor->notify != nullptr) {
158 reactor->notify(reactor->ctx->engine->scheduler, channel);
159 }
160}
161
162void srn_reactor_op_done(srn_reactor_t *reactor, size_t channel) {
163 PANIC_IF_NULL(reactor);
164 PANIC_IF(channel >= reactor->nchannels, "reactor: channel id out of range");
165 reactor->channels[channel].inflight--;
166 atomic_fetch_sub(&reactor->inflight, 1);
167}
168
170 PANIC_IF_NULL(reactor);
171 return atomic_load(&reactor->inflight) == 0;
172}
173
175 srn_reactor_t *reactor, size_t channel, const srn_reactor_io_request_t *request
176) {
177 PANIC_IF_NULL(reactor);
178 PANIC_IF_NULL(request);
179 PANIC_IF(channel >= reactor->nchannels, "reactor: channel id out of range");
180
181 // A clean panic beats what follows otherwise, the backend state is gone
182 // after shutdown, so the wake below would dereference null.
183 PANIC_IF(
184 !atomic_load_explicit(&reactor->running, memory_order_acquire),
185 "reactor: submit on a reactor that is not running"
186 );
187
188 srn_reactor_io_channel_t *ch = &reactor->channels[channel];
189
190 // Admission control. A channel holds at most one ring's worth of ops in
191 // flight, counted from submit until the completion is consumed. The cap is
192 // what makes ring overflow impossible, the SQ holds at most the admitted
193 // ops, and the CQ holds at most the admitted ops minus the one being
194 // posted, so neither the push below nor the post on the reactor thread can
195 // find its ring full. A rejected submit is failed back to the caller as a
196 // transient condition; it clears as the worker consumes completions.
197 if (ch->inflight >= reactor->ring_capacity) {
198 return false;
199 }
200 ch->inflight++;
201
202 // Count the op as in flight BEFORE the request becomes visible to the reactor
203 // thread. If the bump came after the push, the reactor could complete the op
204 // and the consumer could call srn_reactor_op_done before the bump ran --
205 // underflowing the counter and breaking quiescence.
206 atomic_fetch_add(&reactor->inflight, 1);
207
208 // The SQ is single-producer, the worker that owns this channel.
209 PANIC_IF(
210 !srn_spsc_ring_push(ch->sq, request),
211 "reactor: submission queue full despite admission control; this is a bug"
212 );
213
214 // Rouse the reactor so it drains the new request at once instead of waiting
215 // out its idle timeout.
216 reactor->backend->wake(reactor);
217 return true;
218}
219
221 srn_reactor_t *reactor, size_t channel, srn_reactor_io_completion_t *out, size_t max
222) {
223 PANIC_IF_NULL(reactor);
224 PANIC_IF_NULL(out);
225 PANIC_IF(channel >= reactor->nchannels, "reactor: channel id out of range");
226
227 srn_reactor_io_channel_t *ch = &reactor->channels[channel];
228 size_t n = 0;
229
230 while (n < max && (int)srn_spsc_ring_pop(ch->cq, &out[n])) {
231 n++;
232 }
233
234 return n;
235}
236
238 PANIC_IF_NULL(reactor);
239 PANIC_IF(channel >= reactor->nchannels, "reactor: channel id out of range");
240 return (!srn_spsc_ring_empty(reactor->channels[channel].cq)) != 0;
241}
242
243// -----------------------------------------------------------------------------
244// The reactor thread
245// -----------------------------------------------------------------------------
246// Timers
247// -----------------------------------------------------------------------------
248// A min-heap of pending timers keyed by absolute monotonic deadline. The heap
249// stores pointers, so each entry is its own srn_mm_malloc allocation, owned by
250// the reactor and freed when the timer fires (or at shutdown). Touched only on
251// the reactor thread, so no locking.
252typedef struct srn_timer_t {
253 /// Absolute CLOCK_MONOTONIC deadline
254 uint64_t deadline_ns;
255 /// Channel the completion is posted to
256 size_t channel;
257 /// Round tripped to the waiting fiber
260
261/**
262 * Order timers by deadline so the soonest sits at the heap root.
263 */
264static bool timer_less(srn_context_t *ctx, const void *a, const void *b) {
265 UNUSED(ctx);
266 return ((const srn_timer_t *)a)->deadline_ns < ((const srn_timer_t *)b)->deadline_ns;
267}
268
269static const pqmh_control_t timer_ctl = {.less = timer_less};
270
271/**
272 * Register a SLEEP: allocate a timer entry and push it onto the heap.
273 */
274static void timer_add(srn_reactor_t *r, size_t channel, const srn_reactor_io_request_t *req) {
275 assert(r != nullptr);
276
277 srn_timer_t *t = srn_mm_malloc(r->ctx->engine->mm, sizeof(srn_timer_t));
278 PANIC_IF_NULL(t);
279
280 t->deadline_ns = req->deadline_ns;
281 t->channel = channel;
282 t->fiber_data = req->fiber_data;
283 pqmh_push(r->ctx, &r->timers, t);
284}
285
286/**
287 * Milliseconds until the soonest deadline (rounded up), clamped to a valid
288 * `epoll_wait` timeout, 0 if a timer is already due, INT_MAX for a far-off
289 * deadline, and -1 (block indefinitely) when no timers are pending.
290 */
292 assert(r != nullptr);
293
294 const srn_timer_t *t = pqmh_peek(&r->timers);
295
296 if (t == nullptr) {
297 return -1;
298 }
299
300 uint64_t now = srn_now_ns();
301 if (t->deadline_ns <= now) {
302 return 0;
303 }
304
305 // Convert the ns remaining to the ms epoll_wait timeout, rounding UP (add
306 // NS_PER_MS-1) so a sub-millisecond remainder waits 1ms rather than 0 -- we
307 // must never wake early and busy-spin firing nothing.
308 uint64_t ms = ((t->deadline_ns - now) + (SRN_NS_PER_MS - 1)) / SRN_NS_PER_MS;
309 return (ms > (uint64_t)INT_MAX) ? INT_MAX : (int)ms;
310}
311
312/**
313 * Fire every timer whose deadline has passed, pop it (in deadline order), post
314 * a zero result completion to its channel, and free the entry.
315 */
317 assert(r != nullptr);
318
319 uint64_t now = srn_now_ns();
320 const srn_timer_t *top;
321
322 while ((top = pqmh_peek(&r->timers)) != nullptr && top->deadline_ns <= now) {
323 void *out = nullptr;
324 UNUSED(pqmh_pop(r->ctx, &r->timers, &out));
325
326 srn_timer_t *t = (srn_timer_t *)out;
327
329 r, t->channel, (srn_reactor_io_completion_t){.fiber_data = t->fiber_data, .result = 0}
330 );
331 srn_mm_free(r->ctx->engine->mm, t);
332 }
333}
334
335// -----------------------------------------------------------------------------
336static void process_request(srn_reactor_t *r, size_t channel, const srn_reactor_io_request_t *req) {
337 switch (req->op) {
340 r, channel, (srn_reactor_io_completion_t){.fiber_data = req->fiber_data, .result = 0}
341 );
342 break;
352 // Descriptor ops go to the backend, it arms the fd and posts the completion
353 // when it is ready. The backend reports its own failures through a
354 // completion, so there is nothing to check here.
355 r->backend->submit(r, channel, req);
356 break;
357
359 timer_add(r, channel, req);
360 break;
361
363 PANIC("reactor: CANCEL is not implemented yet");
364 }
365}
366
369 // TODO(lxsameer): Should we make this configurable via a policy or something?
370 // For example, pop from each channel once vs pop all the SQEs per channel
371 for (size_t c = 0; c < r->nchannels; c++) {
372 while (srn_spsc_ring_pop(r->channels[c].sq, &req)) {
373 process_request(r, c, &req);
374 }
375 }
376}
377
378static void reactor_thread_main(void *arg) {
379 srn_reactor_t *r = (srn_reactor_t *)arg;
380 while (atomic_load_explicit(&r->running, memory_order_acquire)) {
382 // Wait until the soonest timer deadline (or indefinitely if none), unless a
383 // kernel event, a submission nudge, or shutdown breaks us out sooner. Then
384 // fire whatever timers came due.
387 }
388}
389
390void srn_reactor_activate(srn_reactor_t *reactor, size_t nchannels, srn_reactor_notify_fn notify) {
391
392 PANIC_IF_NULL(reactor);
393 PANIC_IF(nchannels == 0, "reactor: at least one channel is required");
394
395 reactor->notify = notify;
396 reactor->nchannels = nchannels;
397
398 // The ring size is a runtime knob; both rings of every channel use it.
399 // Validate the knobs once here, so everything sized from them downstream
400 // (rings, the backend op pool, the reap stack buffers) is sound, a zero
401 // batch is a zero length VLA, and an oversized magnitude wraps the pool
402 // arithmetic.
403 const size_t ring_mag = reactor->ctx->engine->config.reactor.ring_magnitude;
404 PANIC_IF(ring_mag < 1 || ring_mag > 16, "reactor: ring_magnitude must be within 1..16");
405 reactor->ring_capacity = (size_t)1 << ring_mag;
406
407 const size_t batch = reactor->ctx->engine->config.reactor.reap_batch;
408 PANIC_IF(
409 batch < 1 || batch > reactor->ring_capacity,
410 "reactor: reap_batch must be within 1..ring capacity"
411 );
412
413 reactor->channels = ALLOCN(reactor->ctx, srn_reactor_io_channel_t, nchannels);
414 PANIC_IF_NULL(reactor->channels);
415
416 for (size_t c = 0; c < nchannels; c++) {
417 reactor->channels[c].inflight = 0;
418 // Setup the SQ of the channel
419 reactor->channels[c].sq =
420 srn_spsc_ring_make(reactor->ctx, ring_mag, sizeof(srn_reactor_io_request_t));
421 PANIC_IF_NULL(reactor->channels[c].sq);
422 PANIC_IF(HAS_ERROR(*reactor->channels[c].sq), reactor->channels[c].sq->maybe_error->msg);
423
424 // Setup the CQ of the channel
425 reactor->channels[c].cq =
426 srn_spsc_ring_make(reactor->ctx, ring_mag, sizeof(srn_reactor_io_completion_t));
427 PANIC_IF_NULL(reactor->channels[c].cq);
428 PANIC_IF(HAS_ERROR(*reactor->channels[c].cq), reactor->channels[c].cq->maybe_error->msg);
429 }
430
431 // The timer heap is reactor thread private. Create it before the thread
432 // spawns.
433 reactor->timers = pqmh_make(reactor->ctx, &timer_ctl, 0);
434
435 // Bring the backend up now that nchannels is known, its op pool is sized from
436 // the channel count, so it cannot be initialized before activation.
437 srn_error_t *err = reactor->backend->init(reactor);
438 PANIC_IF(err != nullptr, err->msg);
439
440 atomic_store_explicit(&reactor->running, true, memory_order_release);
441 PANIC_IF(
443 "reactor: failed to spawn the reactor thread"
444 );
445}
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:415
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:165
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:390
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
static void drain_submissions(srn_reactor_t *r)
Definition reactor.c:367
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:291
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
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:264
static const pqmh_control_t timer_ctl
Definition reactor.c:269
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
static void process_request(srn_reactor_t *r, size_t channel, const srn_reactor_io_request_t *req)
Definition reactor.c:336
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:316
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:274
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
static void reactor_thread_main(void *arg)
Definition reactor.c:378
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:174
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:177
@ 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
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:156
A submission, one request a fiber places on its channel.
Definition reactor.h:113
srn_reactor_io_op_t op
Definition reactor.h:114
uint64_t deadline_ns
The wake deadline for SRN_REACTOR_IO_SLEEP, on the monotonic clock, in nanoseconds.
Definition reactor.h:128
void * fiber_data
Opaque token, round tripped to the matching completion.
Definition reactor.h:133
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:258
size_t channel
Channel the completion is posted to.
Definition reactor.c:256
uint64_t deadline_ns
Absolute CLOCK_MONOTONIC deadline.
Definition reactor.c:254
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