|
Serene Runtime 1.0.0
C runtime for the Serene programming language
|
#include <stdatomic.h>#include "serene/rt/context.h"#include "serene/rt/engine.h"#include "serene/rt/fiber.h"#include "serene/rt/fiber/thread.h"#include "serene/rt/mm/interface.h"#include "serene/utils.h"Go to the source code of this file.
Data Structures | |
| struct | srn_scheduler_t |
| struct | srn_worker_t |
| The state one os thread uses to run fibers. More... | |
Macros | |
| #define | SCHED_LOG(FMT, ...) |
| #define | SCHED_TRACE(...) |
| Per-operation deque and queue tracing (push, pop, steal, wake). | |
| #define | SRN_MAX_WORKERS 256 |
| Upper bound on workers per run. | |
| #define | SRN_FIBER_LOCAL_RING_CAP 256 |
| Capacity of each worker's local work-stealing deque. | |
Typedefs | |
| typedef struct srn_worker_t | srn_worker_t |
Defined here, not in fiber.h, which only forward declares srn_scheduler_t. | |
| typedef enum srn_sched_state_t | srn_sched_state_t |
| The scheduler's lifecycle as one atomic value. | |
Enumerations | |
| enum | srn_sched_state_t { SRN_SCHED_IDLE , SRN_SCHED_RUNNING , SRN_SCHED_STOPPING } |
| The scheduler's lifecycle as one atomic value. More... | |
Functions | |
| srn_scheduler_t * | srn_sched_init (srn_engine_t *engine) |
| static void | registry_add (srn_scheduler_t *sched, srn_fiber_t *fiber) |
Insert at the head of the registry. Caller must hold sched->lock. | |
| static void | registry_remove (srn_scheduler_t *sched, srn_fiber_t *fiber) |
| Unlink from the registry. | |
| void | srn_sched_register (srn_scheduler_t *sched, srn_fiber_t *fiber) |
| Record a fiber in the scheduler's registry of live fibers, where it stays until it is reaped. | |
| void | srn_sched_shutdown (srn_scheduler_t *sched) |
The one stop tear down of the fiber subsystem, should be called once srn_sched_run has returned. | |
| static void | announce_work (srn_scheduler_t *sched) |
| Wake the os thread of one parked worker after a fiber has joined a queue. | |
| static bool | local_push (srn_worker_t *w, srn_fiber_t *fiber) |
| This operation is only for the owner of the ring. | |
| static srn_fiber_t * | local_pop (srn_worker_t *w) |
| Owner only. | |
| static srn_fiber_t * | local_steal (srn_worker_t *victim) |
| Thief side. | |
| static void | global_enqueue (srn_scheduler_t *sched, srn_fiber_t *fiber) |
| Append a fiber to the global/overflow queue. | |
| static srn_fiber_t * | global_take (srn_scheduler_t *sched) |
| Pop the head of the global queue, or null when empty. | |
| static void | push_ready (srn_scheduler_t *sched, srn_fiber_t *fiber) |
Put a runnable fiber on a queue, with its state already set to READY. | |
| void | srn_sched_enqueue (srn_scheduler_t *sched, srn_fiber_t *fiber) |
| Place a fiber on a scheduler's ready queue, making it eligible to run. | |
| static void | ready_fiber (srn_scheduler_t *sched, srn_fiber_t *fiber) |
Wake a parked fiber by flipping SUSPENDED to READY and enqueuing it. | |
| static srn_fiber_t * | find_work (srn_worker_t *w) |
| Find a fiber to run: the worker's own deque first, then the global queue, then a steal of one fiber from each peer in turn. | |
| static void | worker_run (srn_worker_t *worker) |
Run the worker routine over worker on the calling os thread. | |
| static void | worker_main (void *arg) |
| The entry an os thread starts in. | |
| void | srn_sched_run (srn_scheduler_t *sched, int nworkers) |
Run the scheduler with nworkers os threads draining it, returning once the pool goes quiescent (every os thread parked on an empty queue) or a stop is requested with srn_sched_stop. | |
| void | srn_sched_stop (srn_scheduler_t *sched) |
| Ask a running scheduler to stop. | |
| void | srn_fiber_yield (void) |
| Yield cooperatively: re-enqueue the running fiber and run the next ready one. | |
| void | srn_fiber_suspend (srn_fiber_park_fn commit, void *arg) |
| A suspended fiber is on no scheduler queue, and the scheduler does not track what it waits on – whoever wakes it does. | |
| void | srn_fiber_ready (srn_fiber_t *fiber) |
| Mark a suspended fiber runnable again. | |
| srn_fiber_t * | srn_fiber_current (void) |
| The fiber currently running on this os thread (the bootstrap fiber if none). | |
| srn_fiber_t * | srn_fiber_worker_loop (void) |
| The worker's loop of the worker running on the calling os thread. | |
| static bool | wait_for_park (srn_fiber_t *self, void *arg) |
| Add the calling fiber to the target's waiter list and stay parked, unless the target has already finished, in which case decline to park so the caller resumes at once. | |
| srn_fiber_result_t | srn_fiber_wait_for (srn_fiber_t *target) |
Block the calling fiber until target finishes, then return its result. | |
Variables | |
| static _Thread_local srn_worker_t * | current_worker = nullptr |
| The worker the calling os thread is running, or null when this os thread is not running the worker routine (srn_sched_run is not active on it). | |
| #define SCHED_LOG | ( | FMT, | |
| ... ) |
Definition at line 28 of file scheduler.c.
| #define SCHED_TRACE | ( | ... | ) |
Per-operation deque and queue tracing (push, pop, steal, wake).
This fires on the hot path, so it floods a debug build and is off unless SRN_SCHED_TRACE is defined. SCHED_LOG (scheduler lifecycle: stop, shutdown reap) stays on in a debug build. Both are silent in release, since DBG is.
Definition at line 37 of file scheduler.c.
| #define SRN_FIBER_LOCAL_RING_CAP 256 |
Capacity of each worker's local work-stealing deque.
Must be a power of two: the live slot for a deque index is index & (cap - 1). 256 matches the common choice (Go, Tokio). A fiber that does not fit overflows to the global queue. This is the single source of truth for the size, so a configuration layer can later drive it.
Definition at line 209 of file scheduler.c.
| #define SRN_MAX_WORKERS 256 |
Upper bound on workers per run.
Clamps the worker count srn_sched_run accepts.
Definition at line 44 of file scheduler.c.
| typedef enum srn_sched_state_t srn_sched_state_t |
The scheduler's lifecycle as one atomic value.
RUNNING means a run is draining the queues. STOPPING tells the workers to wind down, set at natural quiescence or by srn_sched_stop. IDLE is the resting state before a run starts. Workers read it without the lock, so it is atomic.
| typedef struct srn_worker_t srn_worker_t |
Defined here, not in fiber.h, which only forward declares srn_scheduler_t.
Consumers hold a srn_scheduler_t * and never see the layout. Two reasons:
fiber.h.Definition at line 119 of file scheduler.c.
| enum srn_sched_state_t |
The scheduler's lifecycle as one atomic value.
RUNNING means a run is draining the queues. STOPPING tells the workers to wind down, set at natural quiescence or by srn_sched_stop. IDLE is the resting state before a run starts. Workers read it without the lock, so it is atomic.
| Enumerator | |
|---|---|
| SRN_SCHED_IDLE | |
| SRN_SCHED_RUNNING | |
| SRN_SCHED_STOPPING | |
Definition at line 125 of file scheduler.c.
|
static |
Wake the os thread of one parked worker after a fiber has joined a queue.
"Parked" means that os thread is asleep in srn_cond_wait because it found no runnable fiber anywhere. This is not a fiber suspending. It is the whole os thread blocked, and the notify wakes it so it looks again.
runnable is bumped first, then idle is read. Paired against the park path, which bumps idle then reads runnable, this ordering means the two sides can never both miss, so a wakeup is never lost. The notify takes the global lock (the condition's lock) but only when an os thread is actually parked, so the common busy case never touches it.
WARNING: this runs with NO lock around the runnable++ and the idle read, so its only tie to the park path is the seq_cst total order. Both must stay seq_cst. RELAX EITHER AND THE WAKEUP CAN BE LOST (an os thread parked with a runnable fiber queued). See the srn_scheduler_t coordination comment for the full reasoning.
Definition at line 402 of file scheduler.c.
|
static |
Find a fiber to run: the worker's own deque first, then the global queue, then a steal of one fiber from each peer in turn.
Null when nothing is runnable anywhere this worker can reach. Decrements runnable for whatever it takes.
Definition at line 616 of file scheduler.c.
|
static |
Append a fiber to the global/overflow queue.
The caller has set its state. The push, the runnable bump, and the wake all run under the global lock, so this path is trivially serialized against the park path and needs no separate ordering argument.
Put a fiber on the global queue and wake a parked os thread if any. Unlike announce_work, the runnable++, the idle read, and the notify all happen under the lock, so this path is safe by mutual exclusion and does not lean on the seq_cst ordering the lockless path does.
Definition at line 530 of file scheduler.c.
|
static |
Pop the head of the global queue, or null when empty.
The runnable adjustment is left to find_work, the only taker.
Definition at line 555 of file scheduler.c.
|
static |
Owner only.
Pop a fiber from the bottom, or null when empty. The seq_cst fence and the compare-and-swap settle the race with a thief over the last element.
Definition at line 455 of file scheduler.c.
|
static |
This operation is only for the owner of the ring.
Push a fiber on the bottom. Returns false when the deque is full, so the caller can overflow it to the global queue. The caller has set state.
Definition at line 426 of file scheduler.c.
|
static |
Thief side.
Take a fiber from victim's top, or null when the deque is empty or a concurrent take won the race – the caller then just moves to the next victim.
Definition at line 494 of file scheduler.c.
|
static |
Put a runnable fiber on a queue, with its state already set to READY.
A fiber enqueued while running on a worker goes onto that worker's local deque, keeping its work local. One enqueued from off a worker (the initial fibers made before the run, or an external waker), or one that does not fit a full local deque, goes to the global queue.
Definition at line 576 of file scheduler.c.
|
static |
Wake a parked fiber by flipping SUSPENDED to READY and enqueuing it.
Only the flip's winner enqueues, so racing wakers cannot double-enqueue it, and a fiber that is not parked is left untouched. The scheduler does not check the awaited condition. A fiber woken early resumes, re-checks, and parks again.
Definition at line 605 of file scheduler.c.
|
static |
Insert at the head of the registry. Caller must hold sched->lock.
Definition at line 277 of file scheduler.c.
|
static |
Unlink from the registry.
O(1), thanks to the back pointer. Caller must hold sched->lock.
Definition at line 290 of file scheduler.c.
| srn_fiber_t * srn_fiber_current | ( | void | ) |
The fiber currently running on this os thread (the bootstrap fiber if none).
Definition at line 930 of file scheduler.c.
| void srn_fiber_ready | ( | srn_fiber_t * | fiber | ) |
Mark a suspended fiber runnable again.
This is the seam an IO reactor, timer, or peer fiber uses to wake a fiber when the event it awaited occurs.
Definition at line 918 of file scheduler.c.
| void srn_fiber_suspend | ( | srn_fiber_park_fn | commit, |
| void * | arg ) |
A suspended fiber is on no scheduler queue, and the scheduler does not track what it waits on – whoever wakes it does.
Park the running fiber until a party calls srn_fiber_ready.
The commit callback runs on the worker's loop side once the fiber has switched out. It hands the fiber's pointer to the event source it blocks on (a peer fiber, a lock's waiter list, the IO reactor's fd table), so that party can call srn_fiber_ready when the awaited event occurs. Running commit only after the suspend completes is what makes the hand-off race free, a waker can never observe a half-suspended fiber. If commit registers the fiber nowhere, it is genuinely lost – a deadlock, like an os thread blocking on a condition nobody signals.
Definition at line 898 of file scheduler.c.
| srn_fiber_result_t srn_fiber_wait_for | ( | srn_fiber_t * | target | ) |
Block the calling fiber until target finishes, then return its result.
Definition at line 963 of file scheduler.c.
| srn_fiber_t * srn_fiber_worker_loop | ( | void | ) |
The worker's loop of the worker running on the calling os thread.
The fiber that resumes when the current fiber yields, suspends, or finishes, the resumer a fiber's launcher hands control back to. Each worker has its own, so this is valid only on an os thread that is currently running the worker routine.
Definition at line 934 of file scheduler.c.
| void srn_fiber_yield | ( | void | ) |
Yield cooperatively: re-enqueue the running fiber and run the next ready one.
Acts on the fiber currently running on this thread.
Definition at line 876 of file scheduler.c.
| void srn_sched_enqueue | ( | srn_scheduler_t * | sched, |
| srn_fiber_t * | fiber ) |
Place a fiber on a scheduler's ready queue, making it eligible to run.
Definition at line 593 of file scheduler.c.
|
nodiscard |
Definition at line 246 of file scheduler.c.
| void srn_sched_register | ( | srn_scheduler_t * | sched, |
| srn_fiber_t * | fiber ) |
Record a fiber in the scheduler's registry of live fibers, where it stays until it is reaped.
Definition at line 305 of file scheduler.c.
| void srn_sched_run | ( | srn_scheduler_t * | sched, |
| int | nworkers ) |
Run the scheduler with nworkers os threads draining it, returning once the pool goes quiescent (every os thread parked on an empty queue) or a stop is requested with srn_sched_stop.
The calling os thread becomes worker 0, so it does not return until then. nworkers is clamped to at least 1; with 1 it is the calling os thread alone, which keeps execution single-threaded and cooperatively ordered. The spawned os threads are not joined here – srn_sched_shutdown joins them as part of tearing the subsystem down.
Definition at line 784 of file scheduler.c.
| void srn_sched_shutdown | ( | srn_scheduler_t * | sched | ) |
The one stop tear down of the fiber subsystem, should be called once srn_sched_run has returned.
It joins the os threads, then releases the stack of every fiber still registered (any left unreaped, such as one suspended with no party to wake it, or one left queued when the run stopped early), and frees the scheduler's own resources. The fiber structs themselves live in context blocks and are reclaimed with those blocks, not here.
The scheduler is NOT usable after this. A later srn_sched_run panics, and a repeated srn_sched_shutdown is a no-op (so the engine can call it unconditionally even after a caller already did).
Must be called from outside the pool, never from an os thread that is running a worker nor from a fiber, and only after srn_sched_run has returned. Calling it on a live pool panics – stop the pool with srn_sched_stop, let srn_sched_run return, then shut down.
Definition at line 314 of file scheduler.c.
| void srn_sched_stop | ( | srn_scheduler_t * | sched | ) |
Ask a running scheduler to stop.
Each worker routine checks the request at the top of each turn and stops once the fiber it is running yields or finishes, so a slice in flight is never cut mid-execution. srn_sched_run then returns. Fibers left queued stay unrun and are reclaimed by srn_sched_shutdown. Does not wait. Safe to call from any os thread, including a signal-driven context or a fiber. A no-op if the scheduler is not running.
Definition at line 849 of file scheduler.c.
|
static |
Add the calling fiber to the target's waiter list and stay parked, unless the target has already finished, in which case decline to park so the caller resumes at once.
The DONE check and the list insert run together under the global lock, which also guards the list against the DONE handler that drains it. So this either sees the target finished and declines, or joins the list before the drain and is woken by it, never lost in between.
Definition at line 945 of file scheduler.c.
|
static |
The entry an os thread starts in.
It sets up its worker's loop – on its own os thread, so the sanitizer captures the right stack bounds – then runs the worker routine until the pool is quiescent. arg is the worker.
Definition at line 778 of file scheduler.c.
|
static |
Run the worker routine over worker on the calling os thread.
Find a fiber, run it, handle how it gave up control, and park when nothing is runnable, until the pool is quiescent. Owns the current_worker thread-local for its duration. The hot path (find_work hitting the local deque, run, re-enqueue on yield) touches only this worker's own lock free deque. The global lock is reached only to park or for the global queue.
Definition at line 652 of file scheduler.c.
|
static |
The worker the calling os thread is running, or null when this os thread is not running the worker routine (srn_sched_run is not active on it).
This thread-local is the seam that resolves the resumer, the current fiber, and "are we in a fiber?" – all per os thread state that cannot live in the single shared scheduler.
Definition at line 240 of file scheduler.c.