Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
reactor.c File Reference
#include "serene/rt/reactor.h"
#include <assert.h>
#include <limits.h>
#include "backend.h"
#include "internal.h"
#include "serene/rt/engine.h"
#include "serene/rt/impl/spsc_ring.h"
#include "serene/rt/mm/interface.h"
#include "serene/utils.h"
Include dependency graph for reactor.c:

Go to the source code of this file.

Data Structures

struct  srn_timer_t

Typedefs

typedef struct srn_timer_t srn_timer_t

Functions

srn_reactor_tsrn_reactor_init (srn_engine_t *engine)
 Allocate the IO reactor from the engine.
void srn_reactor_shutdown (srn_reactor_t *reactor)
 Tear the reactor down, stop and join the reactor thread and release its channels.
srn_error_tsrn_reactor_errors_static (srn_error_tag_t tag)
 Return the shared, immutable error for an IO failure tag.
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 notify mechanism.
void srn_reactor_op_done (srn_reactor_t *reactor, size_t channel)
 Drop channel's and the reactor's in-flight counts by one.
bool srn_reactor_idle (srn_reactor_t *reactor)
 Whether the reactor has no operations in flight.
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.
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.
bool srn_reactor_channel_has_completions (srn_reactor_t *reactor, size_t channel)
 Whether channel's completion queue has unconsumed completions.
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.
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.
static int timer_next_timeout (srn_reactor_t *r)
 Milliseconds until the soonest deadline (rounded up), clamped to a valid epoll_wait timeout, 0 if a timer is already due, INT_MAX for a far-off deadline, and -1 (block indefinitely) when no timers are pending.
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 to its channel, and free the entry.
static void process_request (srn_reactor_t *r, size_t channel, const srn_reactor_io_request_t *req)
static void drain_submissions (srn_reactor_t *r)
static void reactor_thread_main (void *arg)
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.

Variables

static srn_error_t io_errors []
static const pqmh_control_t timer_ctl = {.less = timer_less}

Typedef Documentation

◆ srn_timer_t

typedef struct srn_timer_t srn_timer_t

Function Documentation

◆ drain_submissions()

void drain_submissions ( srn_reactor_t * r)
static

Definition at line 367 of file reactor.c.

367 {
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}
static void process_request(srn_reactor_t *r, size_t channel, const srn_reactor_io_request_t *req)
Definition reactor.c:336
bool srn_spsc_ring_pop(srn_spsc_ring_t *r, void *out)
Consumer side.
Definition spsc_ring.c:83
srn_spsc_ring_t * sq
Definition internal.h:32
A submission, one request a fiber places on its channel.
Definition reactor.h:113
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
size_t nchannels
Definition internal.h:57
Here is the call graph for this function:
Here is the caller graph for this function:

◆ process_request()

void process_request ( srn_reactor_t * r,
size_t channel,
const srn_reactor_io_request_t * req )
static

Definition at line 336 of file reactor.c.

336 {
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}
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
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
@ 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
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
A completion, the result of one submission, echoed back on the channel.
Definition reactor.h:156
srn_reactor_io_op_t op
Definition reactor.h:114
void * fiber_data
Opaque token, round tripped to the matching completion.
Definition reactor.h:133
const srn_reactor_backend_t * backend
The chosen kernel mechanism and its private state, set at activation.
Definition internal.h:47
#define PANIC(msg)
Definition utils.h:53
Here is the call graph for this function:
Here is the caller graph for this function:

◆ reactor_thread_main()

void reactor_thread_main ( void * arg)
static

Definition at line 378 of file reactor.c.

378 {
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}
static void drain_submissions(srn_reactor_t *r)
Definition reactor.c:367
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
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
void(* wait)(srn_reactor_t *reactor, int timeout_ms)
Wait for readiness and complete any ready operations.
Definition backend.h:75
_Atomic bool running
Definition internal.h:66
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_reactor_activate()

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.

notify is called with the scheduler and the channel id whenever a channel gains completions.

Definition at line 390 of file reactor.c.

390 {
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}
#define ALLOCN(ctx, T, N)
Definition context.h:85
#define HAS_ERROR(x)
True when x – a value carrying an ERROR_HEADER – has an error attached.
Definition errors.h:63
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
static const pqmh_control_t timer_ctl
Definition reactor.c:269
static void reactor_thread_main(void *arg)
Definition reactor.c:378
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
srn_reactor_config_t reactor
srn_engine_t * engine
Long term state of the compiler.
Definition context.h:49
srn_configuration_t config
The runtime's tunable knobs, the single source for every configurable value (see configuration....
Definition engine.h:62
A runtime error, a tag classifying the failure and a human-readable message.
Definition errors.h:125
char * msg
Definition errors.h:127
srn_error_t *(* init)(srn_reactor_t *reactor)
Initialize the backend in the given reactor.
Definition backend.h:64
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.
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_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_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
@ 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 PANIC_IF(cond, msg)
Definition utils.h:59
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_reactor_channel_has_completions()

bool srn_reactor_channel_has_completions ( srn_reactor_t * reactor,
size_t channel )
nodiscard

Whether channel's completion queue has unconsumed completions.

The worker checks this under the scheduler lock before parking, so a completion landing just before it sleeps is not missed.

Definition at line 237 of file reactor.c.

237 {
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}
bool srn_spsc_ring_empty(const srn_spsc_ring_t *r)
Whether the ring currently holds no elements.
Definition spsc_ring.c:103
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_reactor_errors_static()

srn_error_t * srn_reactor_errors_static ( srn_error_tag_t tag)

Return the shared, immutable error for an IO failure tag.

These live in static storage, not a context, so a completion or result can point one at maybe_error with no allocation and no lifetime concern, and it is safe to share across threads. A backend maps its native code (via srn_errno_to_tag on POSIX) to a tag, then to one of these. Any tag without a dedicated entry yields the UNKNOWN_IO_ERROR error.

Definition at line 125 of file reactor.c.

125 {
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}
static srn_error_t io_errors[]
Definition reactor.c:99
Here is the caller graph for this function:

◆ srn_reactor_idle()

bool srn_reactor_idle ( srn_reactor_t * reactor)
nodiscard

Whether the reactor has no operations in flight.

The scheduler folds this into quiescence, a pool with parked workers and an empty run queue is only truly done when the reactor is idle too.

Definition at line 169 of file reactor.c.

169 {
170 PANIC_IF_NULL(reactor);
171 return atomic_load(&reactor->inflight) == 0;
172}
_Atomic size_t inflight
Operations submitted but not yet consumed, across all channels.
Definition internal.h:71
Here is the caller graph for this function:

◆ srn_reactor_init()

srn_reactor_t * srn_reactor_init ( srn_engine_t * engine)
nodiscard

Allocate the IO reactor from the engine.

Definition at line 34 of file reactor.c.

34 {
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}
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
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
srn_mm_t * mm
Memory manager.
Definition engine.h:65
srn_reactor_backend_e backend
What IO backend to use.
void * backend_state
Definition internal.h:48
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_reactor_op_done()

void srn_reactor_op_done ( srn_reactor_t * reactor,
size_t channel )

Drop channel's and the reactor's in-flight counts by one.

The consumer of a completion calls this after it has finished acting on it (for fibers, after the waiting fiber is readied), so that "in flight" reaches zero only once every result has been both produced and handled. Dropping the channel count is also what re-opens the channel's admission window (see srn_reactor_submit).

Definition at line 162 of file reactor.c.

162 {
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}
Here is the caller graph for this function:

◆ srn_reactor_post_completion()

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 notify mechanism.

Runs on the reactor thread, called by the core for ops it completes itself (such as NOP) and by a backend when a descriptor operation finishes. It does NOT drop the in-flight count: that is the consumer's job via srn_reactor_op_done, so an op counts as gone only once the waiting fiber has been readied, never in the gap between.

Definition at line 141 of file reactor.c.

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}
bool srn_spsc_ring_push(srn_spsc_ring_t *r, const void *elem)
Producer side.
Definition spsc_ring.c:61
srn_scheduler_t * scheduler
The fiber scheduler, that is the entry point of the fiber subsystem.
Definition engine.h:75
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_reactor_reap()

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 at line 220 of file reactor.c.

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}
int n
Definition acutest.h:525
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_reactor_shutdown()

void srn_reactor_shutdown ( srn_reactor_t * reactor)

Tear the reactor down, stop and join the reactor thread and release its channels.

Definition at line 66 of file reactor.c.

66 {
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}
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 pqmh_free(srn_context_t *ctx, pqmh_t *pq)
Release the backing array and reset the queue to empty.
Definition pqmh.c:142
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(* shutdown)(srn_reactor_t *reactor)
Clean up after the backend.
Definition backend.h:67
void(* wake)(srn_reactor_t *reactor)
Definition backend.h:77
srn_thread_status_t srn_thread_join(srn_thread_t *t)
Block until the thread started for t returns.
#define RETURN_IF_NULL(ptr)
Definition utils.h:67
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_reactor_submit()

bool srn_reactor_submit ( srn_reactor_t * reactor,
size_t channel,
const srn_reactor_io_request_t * request )
nodiscard

Place a request on channel's submission queue and rouse the reactor.

Returns false without submitting when the channel already has a full ring's worth of operations in flight. The condition is transient, it clears as the channel's worker consumes completions, so the caller can fail the operation back with CHANNEL_BUSY and let the fiber retry.

Definition at line 174 of file reactor.c.

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}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ timer_add()

void timer_add ( srn_reactor_t * r,
size_t channel,
const srn_reactor_io_request_t * req )
static

Register a SLEEP: allocate a timer entry and push it onto the heap.

Definition at line 274 of file reactor.c.

274 {
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}
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
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
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
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
Here is the call graph for this function:
Here is the caller graph for this function:

◆ timer_fire_expired()

void timer_fire_expired ( srn_reactor_t * r)
static

Fire every timer whose deadline has passed, pop it (in deadline order), post a zero result completion to its channel, and free the entry.

Definition at line 316 of file reactor.c.

316 {
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}
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
uint64_t srn_now_ns(void)
The current time on the monotonic clock, in nanoseconds.
Definition utils.c:46
#define UNUSED(x)
Definition utils.h:45
Here is the call graph for this function:
Here is the caller graph for this function:

◆ timer_less()

bool timer_less ( srn_context_t * ctx,
const void * a,
const void * b )
static

Order timers by deadline so the soonest sits at the heap root.

Definition at line 264 of file reactor.c.

264 {
265 UNUSED(ctx);
266 return ((const srn_timer_t *)a)->deadline_ns < ((const srn_timer_t *)b)->deadline_ns;
267}

◆ timer_next_timeout()

int timer_next_timeout ( srn_reactor_t * r)
static

Milliseconds until the soonest deadline (rounded up), clamped to a valid epoll_wait timeout, 0 if a timer is already due, INT_MAX for a far-off deadline, and -1 (block indefinitely) when no timers are pending.

Definition at line 291 of file reactor.c.

291 {
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}
#define SRN_NS_PER_MS
nanoseconds in one millisecond
Definition utils.h:308
Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ io_errors

srn_error_t io_errors[]
static
Initial value:
= {
{CONNECTION_REFUSED, (char *)"connection refused"},
{CONNECTION_RESET, (char *)"connection reset by peer"},
{CONNECTION_ABORTED, (char *)"connection aborted"},
{BROKEN_PIPE, (char *)"broken pipe"},
{BAD_DESCRIPTOR, (char *)"bad file descriptor"},
{TIMED_OUT, (char *)"operation timed out"},
{CANCELED, (char *)"operation canceled"},
{NOT_CONNECTED, (char *)"socket is not connected"},
{ADDRESS_IN_USE, (char *)"address already in use"},
{HOST_UNREACHABLE, (char *)"host is unreachable"},
{NETWORK_UNREACHABLE, (char *)"network is unreachable"},
{MESSAGE_TOO_LONG, (char *)"message too long"},
{TOO_MANY_OPEN_FILES, (char *)"too many open files"},
{PERMISSION_DENIED, (char *)"permission denied"},
{OUT_OF_MEMORY, (char *)"out of memory"},
{NOT_FOUND, (char *)"no such file or directory"},
{ALREADY_EXISTS, (char *)"file already exists"},
{IS_A_DIRECTORY, (char *)"is a directory"},
{NOT_A_DIRECTORY, (char *)"not a directory"},
{NO_SPACE, (char *)"no space left on device"},
{INVALID_ARGUMENT, (char *)"invalid argument"},
{CHANNEL_BUSY, (char *)"reactor channel is at capacity"},
{UNKNOWN_IO_ERROR, (char *)"unknown IO error"},
}
@ 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

Definition at line 99 of file reactor.c.

99 {
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};

◆ timer_ctl

const pqmh_control_t timer_ctl = {.less = timer_less}
static

Definition at line 269 of file reactor.c.

269{.less = timer_less};
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