Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
fiber.h
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
19// TODO(lxsameer): A bocking IO thread poool:
20// The current implementation has a big weakness. Depending on the backend it
21// might to NOT support async operations on regular files (Likes of epoll,
22// kqueue backend; while io_uring and IOCP do). That means that we are facing
23// with a tricky situation.
24//
25// We can never block the reactor since it is single threaded. We need to either
26// offload the blocking IO operation to a threadpool dedicated for such
27// operations or just do the blocking IO in the fibers themselves.
28// Right now we went with the later option since it is much, much simpler to
29// implement, and we didn't want to overengineer. But the better choice seem to
30// be the thread pool option.
31//
32// That being said, it has it's own problems as well. For example:
33// - We need more parked threads that might not be optimal
34// - We can't post completion from the threadpool since SQs/CQs are single
35// producer/consumer and we need to have Multi producer signle user queues.
36// - The current model would be ok for the majority of use cases.
37//
38// So I guess the point of this todo is to investigate the IO threadpool idea,
39// and implement it if makes sense.
40
41/** @file
42 AI Generated (🤦)
43 Fiber subsystem overview
44
45 Stackful, cooperative fibers for the runtime. A fiber is a function plus its
46 own stack, so it can pause at any call depth and resume later. Nothing is
47 preempted. A fiber runs until it explicitly yields, suspends, or finishes.
48
49 Terminology (used throughout the fiber subsystem)
50 fiber a unit of work with its own stack. It can pause and resume.
51 os thread a thread the operating system schedules.
52 worker the state one os thread uses to run fibers. It holds a local
53 queue of ready fibers and a worker's loop. There is one
54 worker per os thread.
55 worker's loop a special fiber that stands in for the os thread. A running
56 fiber switches back to it to hand control over, and it picks
57 the next fiber to run.
58 worker routine the steps an os thread repeats. It takes a fiber, switches
59 into it, deals with how the fiber gave up control, and parks
60 when no fiber is runnable.
61 scheduler there is exactly one. It owns the shared ready queue and the
62 list of live fibers and decides what runs next. It does not
63 run fibers itself, workers do.
64 suspend a fiber steps off every queue and waits to be woken. Its os
65 thread stays free to run other fibers.
66 park an os thread blocks because no fiber is runnable anywhere. A
67 notify wakes it.
68
69 Every fiber stack is the same fixed size (SRN_FIBER_DEFAULT_STACK_SIZE), one
70 mapping with a guard page that never grows. Uniform size is a deliberate
71 constraint, not an accident. It lets a finished fiber's stack be handed to any
72 later fiber (the stack ring TODO below) without tracking size classes, and it
73 keeps switch and mapping costs predictable.
74
75 Control flow is a hub. A fiber never jumps to another fiber. It switches back
76 to its worker's loop, which picks the next one.
77
78 scheduler ready queue: [F2] -> [F5] -> ...
79 | the os thread takes the head
80 v
81 +-----------> worker's loop --- switch ---> running fiber
82 | |
83 | yield (re-enqueue, run next) ----------------+
84 | suspend (off-queue until srn_fiber_ready) -----+
85 | finish (reap) --------------------------------+
86 +--------------------------------------------------+
87
88 A running fiber gives up control three ways:
89 - yield still runnable, so it moves to the back of the ready queue.
90 - suspend off every queue until some party calls srn_fiber_ready. Fibers,
91 workers, and the reactor may wake it; the reactor is the only
92 waker outside the worker pool (see srn_fiber_ready).
93 - finish its entry returns and the fiber is reaped.
94
95 Signal handling is not part of this subsystem. No scheduler entry point is
96 async signal safe, and by design: a runtime that owns an event loop folds
97 signals into it rather than promising handler safety call by call.
98 `srn_sched_stop` and `srn_sched_drain` take the scheduler mutex and signal
99 its condition variable, so calling them from a signal handler can
100 self-deadlock. A signal-initiated shutdown routes the signal into ordinary
101 thread context first (a dedicated thread in sigwait, or a signalfd
102 registered with the reactor) and calls stop or drain from there.
103
104 The spec book's fibers chapter (docs/spec/fibers.typ) is the long-form
105 reference. Keep this overview in step with it.
106*/
107
108#pragma once
109
110#include <stddef.h>
111#include <stdint.h>
112
113#ifndef __x86_64__
114# error "no fiber context switch for this architecture (need ctx_<arch>.c and switch_<arch>.S)"
115#endif
116
117// Sanitizer detection. AddressSanitizer and ThreadSanitizer each need a little
118// per-fiber bookkeeping (the guarded fields below and the switch path in
119// fiber.c), and a normal build carries none of it. Every translation unit in a
120// build compiles with the same `-fsanitize` flags, so the struct layout stays
121// consistent within that build.
122#if defined(__SANITIZE_ADDRESS__)
123# define SRN_ASAN 1
124#elif defined(__has_feature)
125# if __has_feature(address_sanitizer)
126# define SRN_ASAN 1
127# endif
128#endif
129#ifndef SRN_ASAN
130# define SRN_ASAN 0
131#endif
132
133#if defined(__SANITIZE_THREAD__)
134# define SRN_TSAN 1
135#elif defined(__has_feature)
136# if __has_feature(thread_sanitizer)
137# define SRN_TSAN 1
138# endif
139#endif
140#ifndef SRN_TSAN
141# define SRN_TSAN 0
142#endif
143
144typedef struct srn_engine_t srn_engine_t;
145typedef struct srn_context_t srn_context_t;
147typedef struct srn_scheduler_t srn_scheduler_t;
148typedef struct srn_reactor_t srn_reactor_t;
149typedef size_t srn_worker_id_t;
150
151/**
152 * What a fiber's entry produces, type-erased. It is an alias for `void *`, so
153 * the fiber subsystem carries no dependency on the value model. A caller that
154 * runs fibers over Serene values casts to and from its own type at the
155 * boundary.
156 */
157typedef void *srn_fiber_result_t;
158
159#define srn_fiber_get_scheduler_m(fiber) fiber->ctx->engine->scheduler
160
161// TODO(lxsameer): We need to have a ring with a certain size, which
162// would be like a storage for ready to use stacks. When we're done with a fiber
163// put its stack in the ring and prepare it for another upcoming fiber.
164// it might be even a good idea to allocate a few stacks in advance on start
165// up. This ring should be per thread I think.
166
167/**
168 * Default size of every fiber stack. All fiber stacks share one size (see the
169 * subsystem overview), so this is a process-wide value, not a per-fiber one:
170 * the default here, eventually overridable through a CLI argument.
171 *
172 * A fiber stack is fixed and cannot grow (see the fibers chapter), so the size
173 * must hold realistic call chains yet stay cheap to reserve in bulk. 128 KiB
174 * strikes that balance, deep enough for the runtime's C and JIT frames, while
175 * lazy page commit means a shallow fiber faults in only the one or two pages
176 * it touches and leaves the rest a virtual reservation. For scale, an OS
177 * thread stack is far too large to spawn fibers by the thousand, and a few KiB
178 * would overflow on modest nesting.
179 */
180#define SRN_FIBER_DEFAULT_STACK_SIZE (128U * 1024U)
181
182/**
183 * Size of the embedded debug name buffer on every fiber, terminator
184 * included. Large enough for the autogenerated `fiber_<id>` spelling of any
185 * 64 bit id.
186 */
187#define SRN_FIBER_NAME_MAX 32U
188
189// -----------------------------------------------------------------------------
190// Saved machine context
191// -----------------------------------------------------------------------------
192/**
193 * The saved context of a suspended fiber is a single word, its stack pointer
194 * at the moment it was switched away from. The callee-saved registers live on
195 * the fiber's own stack, pushed by srn_fiber_swap and popped when it is
196 * resumed.
197 */
201
202// -----------------------------------------------------------------------------
203// Stack
204// -----------------------------------------------------------------------------
205/**
206 * One stack per fiber, mapped with a guard page at the low end so an overflow
207 * faults deterministically instead of corrupting a neighbour. With addresses
208 * increasing to the right, the layout is:
209 *
210 * |... guard page ...|.......... frames ..........|
211 * ^ guard ^ limit ^ start
212 *
213 * `guard` is the mmap base, the lowest page of the region. The stack grows
214 * downward, so frames start at the high end (`start`) and grow toward
215 * `limit`; one step past `limit` lands on the protected guard page and
216 * faults.
217 */
218typedef struct srn_fiber_stack_t {
219 /// High end, stack pointer initialises to this address
220 void *start;
221 /// Low end of usable region
222 void *limit;
223 /// The protected page to detect stack overflows
224 void *guard;
226
227// -----------------------------------------------------------------------------
228// Lifecycle
229// -----------------------------------------------------------------------------
230typedef enum srn_fiber_state_e : uint8_t {
231 /// Created, stack mapped, never resumed
233 /// On the run queue, eligible to run
235 /// Currently executing
237 /// Parked off the run queue, awaits srn_fiber_ready
239 /// Entry returned. The result is final
242
243/**
244 * The function a fiber runs. Allocations made through `ctx` land in the
245 * context's block.
246 * TODO(lxsameer): The shape is generic until code generation produces
247 * fibers, at which point it adopts the Serene ABI.
248 * TODO(lxsameer): Should we support two types of entry functions? one
249 * for C calling convention and one for FastC?
250 */
252
253/**
254 * Suspend commit callback. The worker routine runs it on its own side once the
255 * fiber has switched out. It re-checks the awaited condition and only then
256 * registers `self` with whatever will wake it, returning true to stay
257 * suspended, or false to resume `self` at once because the condition already
258 * holds. The order matters, registering before the re-check can leave a live
259 * registration behind a false return, and that stale waker would wake the
260 * fiber's NEXT suspend. It must not switch fibers.
261 */
262typedef bool (*srn_fiber_park_fn)(srn_fiber_t *self, void *arg);
263
265 /// Saved stack pointer (see srn_fiber_ctx_t)
268 /// The lifecycle state. Atomic because a waker may run on a different os
269 /// thread than the worker running the fiber. `srn_fiber_ready` flips
270 /// `SUSPENDED` to `READY` with a CAS while the worker running the fiber reads
271 /// and writes the same field. Making it atomic keeps every access whole, so a
272 /// read can never see an inconsistent value.
276 void *arg;
277 /// Set when state reaches SRN_FIBER_DONE. Type-erased (see
278 /// srn_fiber_result_t), the caller interprets it.
280
281 /// While this fiber is suspending, the commit the worker routine runs once
282 /// the fiber is off the stack, to publish it to its waker (see
283 /// srn_fiber_suspend). Valid only across that one suspend hand-off.
285 void *park_arg;
286
287#if SRN_ASAN
288 /// AddressSanitizer bookkeeping across switches.
289 void *fake_stack;
290#endif
291
292#if SRN_TSAN
293 /// ThreadSanitizer fiber handle. TSan tracks each fiber as its own execution
294 /// context, so the switch path tells it which one is current. Created with
295 /// the fiber and destroyed when it is reaped.
296 void *tsan_fiber;
297#endif
298
299 /// Intrusive link threading this fiber onto one of the scheduler's
300 /// singly-linked lists (the ready run queue, or a wait queue) without a
301 /// separate node allocation, the next pointer lives in the fiber itself. A
302 /// fiber belongs to at most one such list at a time, so one link suffices.
304
305 /// Head of the list of fibers blocked in srn_fiber_wait_for on this fiber.
306 /// They are threaded through their own `link` (a waiter is suspended, so off
307 /// the ready queue, and its `link` is free). When this fiber reaches DONE the
308 /// worker routine wakes them all.
310
311 /// Registry links.
312 ///
313 /// The scheduler keeps every live fiber on one doubly linked list through
314 /// these pointers. It is different from `link`. `link` says where a fiber
315 /// sits in the run order, the registry says the fiber exists at all. A fiber
316 /// joins the list when it is created and leaves when it is reaped, in
317 /// whatever state it is in between -- including SUSPENDED, which sits on no
318 /// queue.
319 ///
320 /// This is how the scheduler accounts for, cleans up, and (later) cancels its
321 /// fibers. Doubly linked so reaping can unlink from the middle in O(1).
324
325 /// Debug name, the caller's choice copied at creation, or the
326 /// autogenerated `f#<id>` from the engine wide object id counter when the
327 /// caller passed none. srn_fiber_init_thread names loop fibers "thread".
328 /// Embedded so the name shares the fiber's lifetime with no separate
329 /// allocation.
331};
332
333// -----------------------------------------------------------------------------
334// Scheduler
335// -----------------------------------------------------------------------------
336[[nodiscard]] [[gnu::nonnull(1)]]
338
339/**
340 * The one stop tear down of the fiber subsystem, should be called once
341 * `srn_sched_run` has returned. It joins the os threads, then releases the
342 * stack of every fiber still registered (any left unreaped, such as one
343 * suspended with no party to wake it, or one left queued when the run stopped
344 * early), and frees the scheduler's own resources. The fiber structs
345 * themselves live in context blocks and are reclaimed with those blocks, not
346 * here.
347 *
348 * The scheduler is NOT usable after this. A later `srn_sched_run` panics, and
349 * a repeated `srn_sched_shutdown` is a no-op (so the engine can call it
350 * unconditionally even after a caller already did).
351 *
352 * Must be called from outside the pool, never from an os thread that is
353 * running a worker nor from a fiber, and only after `srn_sched_run` has
354 * returned. Calling it on a live pool panics -- stop the pool with
355 * `srn_sched_stop`, let `srn_sched_run` return, then shut down.
356 */
357[[gnu::nonnull(1)]]
359
360/**
361 * Run the scheduler with `nworkers` os threads draining it, returning once the
362 * pool goes quiescent (every os thread parked on an empty queue) or a stop is
363 * requested with `srn_sched_stop`. The calling os thread becomes worker 0, so
364 * it does not return until then. `nworkers` is clamped to at least 1; with 1
365 * it is the calling os thread alone, which keeps execution single-threaded and
366 * cooperatively ordered. The spawned os threads are not joined here --
367 * `srn_sched_shutdown` joins them as part of tearing the subsystem down.
368 */
369[[gnu::nonnull(1)]]
370void srn_sched_run(srn_scheduler_t *sched, size_t nworkers);
371
372/**
373 * Ask a running scheduler to stop. Each worker routine checks the request at
374 * the top of each turn and stops once the fiber it is running yields or
375 * finishes, so a slice in flight is never cut mid-execution. `srn_sched_run`
376 * then returns. Fibers left queued stay unrun and are reclaimed by
377 * `srn_sched_shutdown`. Does not wait. Safe to call from any os thread or a
378 * fiber, but NOT from a signal handler. It takes the scheduler mutex and
379 * signals the condition variable, neither of which is async signal safe.
380 */
381[[gnu::nonnull(1)]]
383
384/**
385 * Ask a running scheduler to wind down gracefully. Unlike `srn_sched_stop`,
386 * the workers keep running every runnable fiber and let in-flight IO complete;
387 * only new IO submissions are fenced, so a fiber's next IO call returns
388 * `-ECANCELED` and the fiber unwinds rather than parking on a fresh op. The
389 * pool then reaches the same quiescence as a natural finish and
390 * `srn_sched_run` returns. An in-flight op that never completes (an idle recv,
391 * a long sleep) stalls the wind-down until it finishes; bounding that needs op
392 * cancellation and is not provided yet. Does not wait. Safe to call from any
393 * os thread or a fiber. A no-op if the scheduler is not running.
394 */
395[[gnu::nonnull(1)]]
397
398/**
399 * Record a fiber in the scheduler's registry of live fibers, where it stays
400 * until it is reaped.
401 */
402[[gnu::nonnull(1, 2)]]
404
405/**
406 * Place a fiber on a scheduler's ready queue, making it eligible to run.
407 */
408[[gnu::nonnull(1, 2)]]
410
411// -----------------------------------------------------------------------------
412// Fibers
413// -----------------------------------------------------------------------------
414/**
415 * Create a fiber that will run entry(ctx, arg), registered with `sched` but
416 * NOT scheduled. The fiber stays NEW until srn_fiber_schedule starts it, so
417 * a caller can build several fibers and wire them up before any of them
418 * runs. Registration at creation means the scheduler reaps every fiber at
419 * shutdown, scheduled or not.
420 *
421 * `name` is a debug label copied into the fiber, truncated to
422 * SRN_FIBER_NAME_MAX - 1 bytes, or nullptr for an autogenerated unique one.
423 * A `stack_size` of 0 selects the configured per fiber default.
424 */
425[[nodiscard]] [[gnu::nonnull(1, 2, 4)]]
427 srn_context_t *ctx, srn_scheduler_t *sched, const char *name, srn_fiber_entry_t entry, void *arg,
428 size_t stack_size
429);
430
431/**
432 * Schedule a NEW fiber, making it eligible to run. A fiber is scheduled
433 * exactly once, scheduling one that is not NEW panics, and waking a
434 * suspended fiber is srn_fiber_ready's job instead.
435 */
436[[gnu::nonnull(1)]]
438
439/**
440 * Make and schedule a fiber with every default, the engine's scheduler, the
441 * configured stack size, and an autogenerated name. The caller keeps
442 * ownership of `arg` and must keep it alive until the fiber is done with
443 * it, srn_fiber_spawn_copy lifts that burden.
444 */
445[[gnu::nonnull(1, 2)]]
447
448/**
449 * srn_fiber_spawn with `size` bytes of `*arg` copied into `ctx` first, so
450 * the fiber owns its argument and the caller's stack frame can die freely.
451 * The copy lives until the context is released, like the fiber itself.
452 */
453[[gnu::nonnull(1, 2, 3)]]
455srn_fiber_spawn_copy(srn_context_t *ctx, srn_fiber_entry_t entry, const void *arg, size_t size);
456
457/**
458 * srn_fiber_spawn_copy for an lvalue, the address and size are taken for
459 * the caller.
460 */
461#define SRN_FIBER_SPAWN_COPY(ctx, entry, value) \
462 srn_fiber_spawn_copy(ctx, entry, &(value), sizeof(value))
463
464/**
465 * Yield cooperatively, re-enqueue the running fiber and run the next ready
466 * one. Acts on the fiber currently running on this thread.
467 */
468void srn_fiber_yield(void);
469
470/**
471 * Park the running fiber until a party calls srn_fiber_ready. The fiber
472 * switches out first. Only then does the scheduler run `commit` (see
473 * srn_fiber_park_fn) to register it and confirm it should stay parked.
474 * Registering only after the park completes is what makes it race free -- a
475 * waker can never observe a half parked fiber. Acts on the fiber currently
476 * running on this thread.
477 */
478[[gnu::nonnull(1)]]
479void srn_fiber_suspend(srn_fiber_park_fn commit, void *arg);
480
481/**
482 * Mark a suspended fiber runnable again, waking it when the event it awaited
483 * occurs. Callable from a fiber, a worker, or the reactor. The reactor is
484 * the only legitimate waker outside the worker pool.
485 */
486// NOTE: Quiescence accounts for runnable fibers and the reactor's inflight ops, nothing else, so a
487// wake from an unrelated os thread can arrive after the run has already ended and be lost, or race
488// the scheduler teardown. A subsystem that wakes fibers from its own threads must be accounted for
489// in quiescence the way the reactor is.
490[[gnu::nonnull(1)]]
491void srn_fiber_ready(srn_fiber_t *fiber);
492
493/**
494 * Block the calling fiber until `target` finishes, then return its result.
495 */
496[[gnu::nonnull(1)]]
498
499/**
500 * The fiber currently running on this os thread, or null when the calling
501 * thread is not a worker or the worker is running its own loop rather than
502 * a fiber.
503 */
505
506/**
507 * The worker's loop of the worker running on the calling os thread. The fiber
508 * that resumes when the current fiber yields, suspends, or finishes, the
509 * resumer a fiber's launcher hands control back to. Each worker has its own,
510 * so this is valid only on an os thread that is currently running the worker
511 * routine.
512 */
513[[gnu::returns_nonnull]]
515
516// -----------------------------------------------------------------------------
517// Context switch
518// -----------------------------------------------------------------------------
519/**
520 * Save the current execution context into `from`, restore `to`, and resume on
521 * `to`'s stack. Returns, on `from`'s stack, when a fiber later switches back
522 * into `from`.
523 */
524[[gnu::nonnull(1, 2)]]
526
527/**
528 * Initialise a fresh fiber context so the first srn_fiber_swap into it begins
529 * executing fn(arg) on `stack`.
530 *
531 * `fn` is the fiber's entry, a `void (*)(void *)` that runs on the new stack
532 * and receives `arg`. It must NEVER return -- there is no frame beneath it, so
533 * a return traps in the trampoline. Instead it transfers control away with
534 * srn_fiber_switch (to yield) or srn_fiber_switch_final (when finished), and
535 * as its first action it calls srn_fiber_on_entry(). Typical shape:
536 *
537 * static void worker(void *arg) {
538 * srn_fiber_on_entry(&scheduler); // hand the switch to the
539 * sanitizer
540 * ... do the fiber's work using arg ...
541 * srn_fiber_switch_final(&scheduler); // finished, never returns
542 * }
543 */
544[[gnu::nonnull(1, 3)]]
546 srn_fiber_ctx_t *fiber_ctx, srn_fiber_stack_t stack, void (*fn)(void *), void *arg
547);
548
549/**
550 * Switch from `from` to `to`, both live fibers, telling AddressSanitizer about
551 * the stack change. `from` resumes where it left off when something later
552 * switches back into it.
553 */
554[[gnu::nonnull(1, 2)]]
556
557/**
558 * Like srn_fiber_switch, but for a fiber that has finished and must not be
559 * resumed, control transfers to `to` and never comes back. The fiber's entry
560 * function therefore ends at this call -- any code after it is unreachable,
561 * which is why this is [[noreturn]]. Nothing is freed here. The spent fiber's
562 * stack is left frozen and reclaimed later by its owner (the scheduler reaps a
563 * finished fiber and frees its stack via srn_fiber_stack_free).
564 */
565[[noreturn]] [[gnu::nonnull(1)]]
567
568/**
569 * Call as the first action inside a fresh fiber's entry. `from` is the fiber
570 * that started this one (the scheduler, or the bootstrap thread fiber). It
571 * completes the switch for AddressSanitizer and, since `from`'s stack bounds
572 * only become discoverable here, records them into `from` so a later switch
573 * back is tracked. `from` may be null. A no-op when the sanitizer is not in
574 * use.
575 */
577
578/**
579 * Call when a finished fiber is reaped, after it has switched away for the
580 * last time. Releases the fiber's ThreadSanitizer handle. A no-op when the
581 * sanitizer is not in use.
582 */
583[[gnu::nonnull(1)]]
584void srn_fiber_on_reap(srn_fiber_t *fiber);
585
586/**
587 * Represent the calling OS thread as the running fiber ("#0"), so the
588 * scheduler or a test can switch away from it and back. The thread's stack
589 * bounds are not queried here (there is no portable way). Under the sanitizer
590 * they are recorded by the first fiber's srn_fiber_on_entry. The saved context
591 * is written by the first switch away.
592 */
593[[gnu::nonnull(1)]]
595
596// -----------------------------------------------------------------------------
597// Stack provider (rt/fiber/stack_*.c)
598// -----------------------------------------------------------------------------
599/**
600 * Allocate a stack of at least `size` usable bytes plus a guard page, or
601 * SRN_FIBER_DEFAULT_STACK_SIZE when `size` is 0. Platform specific.
602 */
603[[nodiscard]]
605
607
608// -----------------------------------------------------------------------------
609// Helpers
610// -----------------------------------------------------------------------------
612 return (size_t)((char *)s.start - (char *)s.limit);
613}
614
615/**
616 * Return the id of the worker that the calling os thread is running, or
617 * SIZE_MAX when the calling thread is not a worker.
618 */
620
621/**
622 * Whether the scheduler still accepts new IO submissions. True only while
623 * RUNNING; false once `srn_sched_drain` or `srn_sched_stop` has begun winding
624 * the pool down. The IO bridge reads this to fence submissions during a
625 * wind-down, so a fiber's IO call comes back cancelled instead of parking on an
626 * op the wind-down would have to wait out.
627 */
628[[gnu::nonnull(1)]]
630
631/**
632 * Rouse parked workers so the owner of `channel` consumes its completions.
633 * The reactor's notify hook. The wake is currently a broadcast, `channel`
634 * names the worker that must look, but every parked worker is notified and
635 * the ones with nothing to do re-park.
636 */
637void srn_sched_wake_worker(srn_scheduler_t *sched, size_t channel);
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 scheduler.c:318
size_t srn_worker_id_t
Definition fiber.h:149
srn_fiber_t * srn_fiber_worker_loop(void)
The worker's loop of the worker running on the calling os thread.
Definition scheduler.c:1062
void srn_fiber_switch_final(srn_fiber_t *to)
Like srn_fiber_switch, but for a fiber that has finished and must not be resumed, control transfers t...
Definition fiber.c:88
void srn_fiber_swap(srn_fiber_ctx_t *from, srn_fiber_ctx_t *to)
Save the current execution context into from, restore to, and resume on to's stack.
void srn_sched_wake_worker(srn_scheduler_t *sched, size_t channel)
Rouse parked workers so the owner of channel consumes its completions.
Definition scheduler.c:1116
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
srn_fiber_result_t srn_fiber_wait_for(srn_fiber_t *target)
Block the calling fiber until target finishes, then return its result.
Definition scheduler.c:1091
srn_fiber_t * srn_fiber_spawn_copy(srn_context_t *ctx, srn_fiber_entry_t entry, const void *arg, size_t size)
srn_fiber_spawn with size bytes of *arg copied into ctx first, so the fiber owns its argument and the...
Definition fiber.c:255
srn_fiber_t * srn_fiber_spawn(srn_context_t *ctx, srn_fiber_entry_t entry, void *arg)
Make and schedule a fiber with every default, the engine's scheduler, the configured stack size,...
Definition fiber.c:247
srn_fiber_stack_t srn_fiber_stack_alloc(size_t size)
Allocate a stack of at least size usable bytes plus a guard page, or SRN_FIBER_DEFAULT_STACK_SIZE whe...
static size_t srn_fiber_stack_size(srn_fiber_stack_t s)
Definition fiber.h:611
void srn_fiber_ctx_make(srn_fiber_ctx_t *fiber_ctx, srn_fiber_stack_t stack, void(*fn)(void *), void *arg)
Initialise a fresh fiber context so the first srn_fiber_swap into it begins executing fn(arg) on stac...
void srn_sched_drain(srn_scheduler_t *sched)
Ask a running scheduler to wind down gracefully.
Definition scheduler.c:963
#define SRN_FIBER_NAME_MAX
Size of the embedded debug name buffer on every fiber, terminator included.
Definition fiber.h:187
void srn_fiber_init_thread(srn_fiber_t *f)
Represent the calling OS thread as the running fiber ("#0"), so the scheduler or a test can switch aw...
Definition fiber.c:154
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.
Definition scheduler.c:327
bool srn_sched_accepting_submissions(srn_scheduler_t *sched)
Whether the scheduler still accepts new IO submissions.
Definition scheduler.c:1108
void srn_fiber_stack_free(srn_fiber_stack_t stack)
void srn_sched_stop(srn_scheduler_t *sched)
Ask a running scheduler to stop.
Definition scheduler.c:939
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 scheduler.c:622
srn_fiber_state_e
Definition fiber.h:230
@ SRN_FIBER_NEW
Created, stack mapped, never resumed.
Definition fiber.h:232
@ SRN_FIBER_RUNNING
Currently executing.
Definition fiber.h:236
@ SRN_FIBER_READY
On the run queue, eligible to run.
Definition fiber.h:234
@ SRN_FIBER_DONE
Entry returned. The result is final.
Definition fiber.h:240
@ SRN_FIBER_SUSPENDED
Parked off the run queue, awaits srn_fiber_ready.
Definition fiber.h:238
srn_fiber_result_t(* srn_fiber_entry_t)(srn_context_t *ctx, void *arg)
The function a fiber runs.
Definition fiber.h:251
srn_scheduler_t * srn_sched_init(srn_engine_t *engine)
Definition scheduler.c:257
srn_fiber_t * srn_fiber_current(void)
The fiber currently running on this os thread, or null when the calling thread is not a worker or the...
Definition scheduler.c:1058
void srn_fiber_switch(srn_fiber_t *from, srn_fiber_t *to)
Switch from from to to, both live fibers, telling AddressSanitizer about the stack change.
Definition fiber.c:66
srn_fiber_t * srn_fiber_make(srn_context_t *ctx, srn_scheduler_t *sched, const char *name, srn_fiber_entry_t entry, void *arg, size_t stack_size)
Create a fiber that will run entry(ctx, arg), registered with sched but NOT scheduled.
Definition fiber.c:201
void srn_fiber_on_entry(srn_fiber_t *from)
Call as the first action inside a fresh fiber's entry.
Definition fiber.c:111
bool(* srn_fiber_park_fn)(srn_fiber_t *self, void *arg)
Suspend commit callback.
Definition fiber.h:262
void srn_fiber_schedule(srn_fiber_t *fiber)
Schedule a NEW fiber, making it eligible to run.
Definition scheduler.c:630
void * srn_fiber_result_t
What a fiber's entry produces, type-erased.
Definition fiber.h:157
enum srn_fiber_state_e srn_fiber_state_t
void srn_sched_run(srn_scheduler_t *sched, size_t nworkers)
Run the scheduler with nworkers os threads draining it, returning once the pool goes quiescent (every...
Definition scheduler.c:843
void srn_fiber_on_reap(srn_fiber_t *fiber)
Call when a finished fiber is reaped, after it has switched away for the last time.
Definition fiber.c:133
void srn_fiber_suspend(srn_fiber_park_fn commit, void *arg)
Park the running fiber until a party calls srn_fiber_ready.
Definition scheduler.c:1024
void srn_fiber_yield(void)
Yield cooperatively, re-enqueue the running fiber and run the next ready one.
Definition scheduler.c:1000
srn_worker_id_t srn_sched_current_worker_id(void)
Return the id of the worker that the calling os thread is running, or SIZE_MAX when the calling threa...
Definition scheduler.c:1102
Engine is a structure to own the long living and main pieces of the compiler.
Definition engine.h:51
The saved context of a suspended fiber is a single word, its stack pointer at the moment it was switc...
Definition fiber.h:198
void * sp
Definition fiber.h:199
One stack per fiber, mapped with a guard page at the low end so an overflow faults deterministically ...
Definition fiber.h:218
void * guard
The protected page to detect stack overflows.
Definition fiber.h:224
void * limit
Low end of usable region.
Definition fiber.h:222
void * start
High end, stack pointer initialises to this address.
Definition fiber.h:220
char name[SRN_FIBER_NAME_MAX]
Debug name, the caller's choice copied at creation, or the autogenerated f#<id> from the engine wide ...
Definition fiber.h:330
srn_fiber_entry_t entry
Definition fiber.h:275
_Atomic srn_fiber_state_t state
The lifecycle state.
Definition fiber.h:273
srn_fiber_t * link
Intrusive link threading this fiber onto one of the scheduler's singly-linked lists (the ready run qu...
Definition fiber.h:303
srn_fiber_t * waiters
Head of the list of fibers blocked in srn_fiber_wait_for on this fiber.
Definition fiber.h:309
void * park_arg
Definition fiber.h:285
srn_fiber_result_t result
Set when state reaches SRN_FIBER_DONE.
Definition fiber.h:279
srn_fiber_park_fn park_commit
While this fiber is suspending, the commit the worker routine runs once the fiber is off the stack,...
Definition fiber.h:284
srn_context_t * ctx
Definition fiber.h:274
srn_fiber_stack_t stack
Definition fiber.h:267
srn_fiber_t * reg_prev
Registry links.
Definition fiber.h:322
void * arg
Definition fiber.h:276
srn_fiber_t * reg_next
Definition fiber.h:323
srn_fiber_ctx_t fiber_ctx
Saved stack pointer (see srn_fiber_ctx_t).
Definition fiber.h:266