52 PANIC(
"IO backend is not implemented yet");
60 atomic_init(&reactor->
running,
false);
69 if (!atomic_load_explicit(&reactor->
running, memory_order_acquire)) {
73 atomic_store_explicit(&reactor->
running,
false, memory_order_release);
105 {
TIMED_OUT, (
char *)
"operation timed out"},
106 {
CANCELED, (
char *)
"operation canceled"},
115 {
NOT_FOUND, (
char *)
"no such file or directory"},
119 {
NO_SPACE, (
char *)
"no space left on device"},
121 {
CHANNEL_BUSY, (
char *)
"reactor channel is at capacity"},
129 for (
size_t i = 0; i < count; i++) {
151 "reactor: completion queue full despite admission control; this is a bug"
157 if (reactor->
notify !=
nullptr) {
166 atomic_fetch_sub(&reactor->
inflight, 1);
171 return atomic_load(&reactor->
inflight) == 0;
184 !atomic_load_explicit(&reactor->
running, memory_order_acquire),
185 "reactor: submit on a reactor that is not running"
206 atomic_fetch_add(&reactor->
inflight, 1);
211 "reactor: submission queue full despite admission control; this is a bug"
275 assert(r !=
nullptr);
292 assert(r !=
nullptr);
309 return (ms > (uint64_t)INT_MAX) ? INT_MAX : (int)ms;
317 assert(r !=
nullptr);
363 PANIC(
"reactor: CANCEL is not implemented yet");
371 for (
size_t c = 0; c < r->
nchannels; c++) {
380 while (atomic_load_explicit(&r->
running, memory_order_acquire)) {
393 PANIC_IF(nchannels == 0,
"reactor: at least one channel is required");
404 PANIC_IF(ring_mag < 1 || ring_mag > 16,
"reactor: ring_magnitude must be within 1..16");
410 "reactor: reap_batch must be within 1..ring capacity"
416 for (
size_t c = 0; c < nchannels; c++) {
440 atomic_store_explicit(&reactor->
running,
true, memory_order_release);
443 "reactor: failed to spawn the reactor thread"
Internal reactor backend interface.
const srn_reactor_backend_t srn_reactor_backend_epoll
srn_context_t * srn_context_make(srn_engine_t *engine)
Make an empty context, by allocating a new memory block.
#define ALLOCN(ctx, T, N)
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.
void srn_mm_free(srn_mm_t *mm, void *ptr)
Release a pointer previously returned by srn_mm_malloc or srn_mm_reallocate.
void * srn_mm_malloc(srn_mm_t *mm, size_t size)
Generic allocations that do not participate in the block based pools.
#define HAS_ERROR(x)
True when x – a value carrying an ERROR_HEADER – has an error attached.
srn_error_tag_t
The kind of a failure.
@ INVALID_ARGUMENT
The request itself is malformed (an empty POLL interest mask, for example), so retrying it unchanged ...
@ CHANNEL_BUSY
The channel already has a full ring's worth of operations in flight.
void pqmh_free(srn_context_t *ctx, pqmh_t *pq)
Release the backing array and reset the queue to empty.
void pqmh_push(srn_context_t *ctx, pqmh_t *pq, void *elem)
Insert elem, growing the backing array (doubling) if it is full.
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...
void * pqmh_peek(const pqmh_t *pq)
The highest priority element without removing it, or NULL when the queue is empty.
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...
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_op_done(srn_reactor_t *reactor, size_t channel)
Drop channel's and the reactor's in-flight counts by one.
static void drain_submissions(srn_reactor_t *r)
srn_error_t * srn_reactor_errors_static(srn_error_tag_t tag)
Return the shared, immutable error for an IO failure tag.
static int timer_next_timeout(srn_reactor_t *r)
Milliseconds until the soonest deadline (rounded up), clamped to a valid epoll_wait timeout,...
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.
static srn_error_t io_errors[]
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 const pqmh_control_t timer_ctl
bool srn_reactor_idle(srn_reactor_t *reactor)
Whether the reactor has no operations in flight.
void srn_reactor_shutdown(srn_reactor_t *reactor)
Tear the reactor down, stop and join the reactor thread and release its channels.
static void process_request(srn_reactor_t *r, size_t channel, const srn_reactor_io_request_t *req)
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...
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.
srn_reactor_t * srn_reactor_init(srn_engine_t *engine)
Allocate the IO reactor from the engine.
bool srn_reactor_channel_has_completions(srn_reactor_t *reactor, size_t channel)
Whether channel's completion queue has unconsumed completions.
static void reactor_thread_main(void *arg)
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...
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.
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,...
@ SRN_REACTOR_IO_SLEEP
Wait out a deadline.
@ SRN_REACTOR_IO_ACCEPT
Accept a pending connection on the listening socket fd, with flags (the accept4 flags).
@ SRN_REACTOR_IO_READ
Read from a descriptor into buf. Uses fd, buf, len, offset.
@ SRN_REACTOR_IO_SENDTO
Send buf/len on fd to the address in addr/addrlen with flags.
@ SRN_REACTOR_IO_NOP
Completes immediately with a zero result.
@ SRN_REACTOR_IO_CONNECT
Connect fd to the address in addr/addrlen.
@ SRN_REACTOR_IO_CANCEL
Cancel one inflight operation, named by its fiber_data.
@ SRN_REACTOR_IO_RECVFROM
Receive a datagram from fd into buf/len with flags.
@ SRN_REACTOR_IO_RECVMSG
Receive into the struct msghdr that buf points at, on fd, with flags (scatter/gather and ancillary da...
@ SRN_REACTOR_IO_WRITE
Write buf to a descriptor. Uses fd, buf, len, offset.
@ SRN_REACTOR_IO_POLL
Wait until fd is ready for the events requested in flags (EPOLLIN/EPOLLOUT) without performing any tr...
@ SRN_REACTOR_IO_SENDMSG
Send the struct msghdr that buf points at, on fd, with flags.
bool srn_spsc_ring_push(srn_spsc_ring_t *r, const void *elem)
Producer side.
bool srn_spsc_ring_empty(const srn_spsc_ring_t *r)
Whether the ring currently holds no elements.
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.
bool srn_spsc_ring_pop(srn_spsc_ring_t *r, void *out)
Consumer side.
A mutable single producer, single consumer ring of fixed size elements.
srn_reactor_config_t reactor
srn_engine_t * engine
Long term state of the compiler.
Engine is a structure to own the long living and main pieces of the compiler.
srn_configuration_t config
The runtime's tunable knobs, the single source for every configurable value (see configuration....
srn_scheduler_t * scheduler
The fiber scheduler, that is the entry point of the fiber subsystem.
srn_mm_t * mm
Memory manager.
A runtime error, a tag classifying the failure and a human-readable message.
void(* shutdown)(srn_reactor_t *reactor)
Clean up after the backend.
void(* wake)(srn_reactor_t *reactor)
void(* submit)(srn_reactor_t *reactor, size_t channel, const srn_reactor_io_request_t *req)
Submit a new request to the backend.
srn_error_t *(* init)(srn_reactor_t *reactor)
Initialize the backend in the given reactor.
void(* wait)(srn_reactor_t *reactor, int timeout_ms)
Wait for readiness and complete any ready operations.
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.
size_t inflight
Operations submitted on this channel and not yet consumed.
A completion, the result of one submission, echoed back on the channel.
A submission, one request a fiber places on its channel.
uint64_t deadline_ns
The wake deadline for SRN_REACTOR_IO_SLEEP, on the monotonic clock, in nanoseconds.
void * fiber_data
Opaque token, round tripped to the matching completion.
srn_thread_t thread
The reactor thread and its run flag.
pqmh_t timers
Pending timers (like SLEEP, and IO deadlines) as a min-heap keyed by absolute monotonic deadline.
srn_reactor_io_channel_t * channels
One channel per worker, there is a 1to1 mapping between worker ids and channel ids.
srn_reactor_notify_fn notify
Wakes the worker that owns a channel a completion landed on.
size_t ring_capacity
Capacity of every ring (1 << ring_magnitude), fixed at activation.
const srn_reactor_backend_t * backend
The chosen kernel mechanism and its private state, set at activation.
_Atomic size_t inflight
Operations submitted but not yet consumed, across all channels.
void * fiber_data
Round tripped to the waiting fiber.
size_t channel
Channel the completion is posted to.
uint64_t deadline_ns
Absolute CLOCK_MONOTONIC deadline.
srn_thread_status_t srn_thread_join(srn_thread_t *t)
Block until the thread started for t returns.
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)
#define SRN_NS_PER_MS
nanoseconds in one millisecond
uint64_t srn_now_ns(void)
The current time on the monotonic clock, in nanoseconds.
#define RETURN_IF_NULL(ptr)
#define PANIC_IF(cond, msg)