Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
reactor.h File Reference

Reactor overview. More...

#include <stddef.h>
#include <stdint.h>
#include "serene/rt/errors.h"
Include dependency graph for reactor.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  srn_reactor_io_request_t
 A submission, one request a fiber places on its channel. More...
struct  srn_reactor_io_completion_t
 A completion, the result of one submission, echoed back on the channel. More...

Typedefs

typedef enum srn_reactor_io_op_t srn_reactor_io_op_t
 The operation a submission asks the reactor to perform.
typedef struct srn_reactor_io_request_t srn_reactor_io_request_t
 A submission, one request a fiber places on its channel.
typedef struct srn_reactor_io_completion_t srn_reactor_io_completion_t
 A completion, the result of one submission, echoed back on the channel.
typedef 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, without the reactor knowing about workers or parking.

Enumerations

enum  srn_reactor_io_op_t {
  SRN_REACTOR_IO_NOP = 0 , SRN_REACTOR_IO_SLEEP , SRN_REACTOR_IO_READ , SRN_REACTOR_IO_WRITE ,
  SRN_REACTOR_IO_CANCEL , SRN_REACTOR_IO_ACCEPT , SRN_REACTOR_IO_CONNECT , SRN_REACTOR_IO_RECVFROM ,
  SRN_REACTOR_IO_SENDTO , SRN_REACTOR_IO_RECVMSG , SRN_REACTOR_IO_SENDMSG , SRN_REACTOR_IO_POLL
}
 The operation a submission asks the reactor to perform. More...

Functions

srn_reactor_tsrn_reactor_init (srn_engine_t *engine)
 Allocate the IO reactor from the engine.
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.
void srn_reactor_shutdown (srn_reactor_t *reactor)
 Tear the reactor down, stop and join the reactor thread and release its channels.
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.
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.
void srn_reactor_consume (srn_reactor_t *reactor, size_t channel)
 Drain and act on channel's completions.
bool srn_reactor_channel_has_completions (srn_reactor_t *reactor, size_t channel)
 Whether channel's completion queue has unconsumed completions.

Detailed Description

Reactor overview.

The reactor is in charge of all the IO in the runtime.

It is completion based and modeled after io_uring; a submission describes a whole request, and a completion reports its result. The reactor utilizes different backends for different platforms. A readiness backend such as epoll performs the transfer itself when the descriptor signals readiness and posts the finished result, so the contract is the common subset every platform can honour.

A worker and the reactor talk through a channel: a channel is a pair of submission queue (SQ) and a completion queue (CQ), a SQ carrying requests toward the reactor and a CQ carrying results back. There is one channel per worker. The reactor owns every channel; a worker holds only the channel id and never touches a raw queue.

Each request carries fiber_data, an opaque token the reactor round-trips untouched: it rides in on the submission and comes back out on the completion, and is how a finished result finds the fiber that was waiting.

Definition in file reactor.h.

Typedef Documentation

◆ srn_reactor_io_completion_t

typedef struct srn_reactor_io_completion_t srn_reactor_io_completion_t

A completion, the result of one submission, echoed back on the channel.

It is a fallible value (see errors.h). maybe_error is null on success and points at the failure otherwise, so result/addrlen are meaningful only when there is no error. The backend sets maybe_error from a static IO error to avoid unnecessary memory allocation.

◆ srn_reactor_io_op_t

The operation a submission asks the reactor to perform.

◆ srn_reactor_io_request_t

typedef struct srn_reactor_io_request_t srn_reactor_io_request_t

A submission, one request a fiber places on its channel.

The reactor copies it into the submission queue, so the struct itself need not outlive the call; a buffer it points at (buf) must stay valid until the operation completes.

◆ srn_reactor_notify_fn

typedef 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, without the reactor knowing about workers or parking.

The reactor calls it after placing completions on channel; the scheduler wakes that channel's worker if it is parked.

Definition at line 177 of file reactor.h.

Enumeration Type Documentation

◆ srn_reactor_io_op_t

The operation a submission asks the reactor to perform.

Enumerator
SRN_REACTOR_IO_NOP 

Completes immediately with a zero result.

Exercises the submit-and-complete path on its own, with no descriptor and no kernel mechanism.

SRN_REACTOR_IO_SLEEP 

Wait out a deadline.

SRN_REACTOR_IO_READ 

Read from a descriptor into buf. Uses fd, buf, len, offset.

SRN_REACTOR_IO_WRITE 

Write buf to a descriptor. Uses fd, buf, len, offset.

SRN_REACTOR_IO_CANCEL 

Cancel one inflight operation, named by its fiber_data.

Not implemented, submitting it panics. It lands together with the fd deregistration work.

SRN_REACTOR_IO_ACCEPT 

Accept a pending connection on the listening socket fd, with flags (the accept4 flags).

The peer address is written to addr/addrlen when addr is non-null. The completion's result is the accepted descriptor; failures arrive through the completion's maybe_error.

SRN_REACTOR_IO_CONNECT 

Connect fd to the address in addr/addrlen.

The connect is started at submit time and completes once the socket is writable. result is 0 on success; failures arrive through the completion's maybe_error.

SRN_REACTOR_IO_RECVFROM 

Receive a datagram from fd into buf/len with flags.

The sender address is written to addr/addrlen when addr is non-null. result is bytes received; failures arrive through the completion's maybe_error.

SRN_REACTOR_IO_SENDTO 

Send buf/len on fd to the address in addr/addrlen with flags.

result is bytes sent; failures arrive through the completion's maybe_error.

SRN_REACTOR_IO_RECVMSG 

Receive into the struct msghdr that buf points at, on fd, with flags (scatter/gather and ancillary data).

result is bytes received; failures arrive through the completion's maybe_error.

SRN_REACTOR_IO_SENDMSG 

Send the struct msghdr that buf points at, on fd, with flags.

result is bytes sent; failures arrive through the completion's maybe_error.

SRN_REACTOR_IO_POLL 

Wait until fd is ready for the events requested in flags (EPOLLIN/EPOLLOUT) without performing any transfer.

result is the ready-events bitmask. The gateway for timerfd, signalfd, eventfd, inotify and pidfd.

Definition at line 60 of file reactor.h.

60 {
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.
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

Function Documentation

◆ 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_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_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
@ 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_consume()

void srn_reactor_consume ( srn_reactor_t * reactor,
size_t channel )

Drain and act on channel's completions.

Called by the worker that owns the channel, from its loop. Per completion it sets the waiter's result, readies the fiber, and drops the in-flight count.

Drain and act on channel's completions.

Drains this worker's channel and, for each completion, copies the outcome into the waiting fiber's waiter, readies it, and only then drops the inflight count, so quiescence never fires in the gap between a result being produced and its fiber being made runnable. srn_fiber_ready here pushes onto the running worker's own deque, keeping the woken fiber local.

Definition at line 121 of file io.c.

121 {
122 // The drain batch is the configured reap batch -- "most completions a worker
123 // drains from its channel in a single pass", which is exactly this loop.
124 size_t batch = reactor->ctx->engine->config.reactor.reap_batch;
125 srn_reactor_io_completion_t comps[batch];
126 size_t n;
127
128 while ((n = srn_reactor_reap(reactor, channel, comps, batch)) > 0) {
129 for (size_t i = 0; i < n; i++) {
130 io_waiter_t *w = (io_waiter_t *)comps[i].fiber_data;
131 w->maybe_error = comps[i].maybe_error;
132 w->result = comps[i].result;
133 w->addrlen = comps[i].addrlen;
135 srn_reactor_op_done(reactor, channel);
136 }
137 }
138}
int n
Definition acutest.h:525
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
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
void srn_fiber_ready(srn_fiber_t *fiber)
Mark a suspended fiber runnable again, waking it when the event it awaited occurs.
Definition scheduler.c:1044
Lives on the suspended fiber's stack; the worker fills it from the completion and the fiber reads it ...
Definition io.c:66
uint32_t addrlen
Definition io.c:70
size_t result
Definition io.c:69
srn_fiber_t * fiber
Definition io.c:68
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
Here is the call graph for this function:
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
#define PANIC(msg)
Definition utils.h:53
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_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}
bool srn_spsc_ring_pop(srn_spsc_ring_t *r, void *out)
Consumer side.
Definition spsc_ring.c:83
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}
bool srn_spsc_ring_push(srn_spsc_ring_t *r, const void *elem)
Producer side.
Definition spsc_ring.c:61
Here is the call graph for this function:
Here is the caller graph for this function: