ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
worker.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file worker.h
6 * @brief Core server - server worker identity.
7 *
8 * The server pipeline runs in one or more dedicated worker tasks (see
9 * PROTOCORE_WORKER_COUNT). Each worker owns a disjoint partition of connection slots
10 * (slot i -> worker i % count) and its own scratch arena, so per-worker state
11 * (the arena, work buffers) is selected by the caller's worker id. This header is
12 * the single source of that id.
13 *
14 * The id is per-task/per-thread: a worker binds itself once at task entry via
15 * protocore_worker_set_self(); any context that has not bound an id (the user's
16 * loop(), a unit test, the network stack's own thread) reads 0, which is also the only valid id
17 * in the default single-worker build, so PROTOCORE_WORKER_COUNT == 1 is byte-for-byte
18 * the original single-pipeline behavior.
19 *
20 * @author Douglas Quigg (dstroy0)
21 * @date 2026
22 */
23
24#ifndef PROTOCORE_WORKER_H
25#define PROTOCORE_WORKER_H
26
27#include "protocore_config.h"
28
29#include "locus_carcerum/locus_carcerum.h" // the cellblock guards each worker slot borrows through
30
31#if PROTOCORE_ENABLE_PREEMPT_QUEUE
32#include "server/core/preempt_queue/preempt_queue.h" // carried below as Session.workers->queue
33#endif
34
36
37// ---------------------------------------------------------------------------
38// Worker identity
39// ---------------------------------------------------------------------------
40
41/** @brief Number of server worker tasks (PROTOCORE_WORKER_COUNT). */
43
44/**
45 * @brief Worker id [0, count) of the calling task; 0 by default / single-worker.
46 *
47 * With PROTOCORE_WORKER_COUNT == 1 (the default) there is exactly one worker, so the answer is 0 by
48 * construction and this is an inline constant - no lookup, no call. Every borrow asks, so the
49 * multi-worker lookup is paid only where there is more than one worker to tell apart.
50 */
51#if PROTOCORE_WORKER_COUNT == 1
53{
54 return 0;
55}
56#else
58#endif
59
60/** @brief Bind the calling task/thread to worker id @p id (worker entry / tests). */
62
63// ---------------------------------------------------------------------------
64// Cellblocks - the memory each worker slot borrows from
65// ---------------------------------------------------------------------------
66//
67// Each slot borrows through two MMgr cellblock guards: a minimum-security one for plaintext, whose
68// bytes are left as they are on release, and a maximum-security one for key material, whose bytes
69// are zeroed on release. A slot has exactly one accessor, the worker that owns it, so a borrow is a
70// plain bump with no lock.
71//
72// The worker slots are the application's: it declares the pools and the LocusCarcerum over them in
73// its own translation unit and hands the guards in through protocore_cellblocks_bind(). The ghost
74// slot (PROTOCORE_GHOST_WORKER_SLOT) is the library's own, declared in worker.c. A slot nothing was
75// bound to, and any context outside [0, PROTOCORE_WORKER_COUNT), borrows from the ghost.
76
77/**
78 * @brief Hand worker slot @p worker the guards it borrows through.
79 *
80 * @param worker a worker id in [0, PROTOCORE_WORKER_COUNT); anything else is ignored.
81 * @param plain the minimum-security guard for plaintext, or NULL to leave the slot on the ghost's.
82 * @param secure the maximum-security guard for key material, or NULL to leave it on the ghost's.
83 */
84void protocore_cellblocks_bind(int worker, const MinimumSecurityGuard *plain, const MaximumSecurityGuard *secure);
85
86/** @brief The plaintext guard the calling worker borrows through. Never NULL. */
87const MinimumSecurityGuard *protocore_plain_guard(void);
88
89/** @brief The key-material guard the calling worker borrows through. Never NULL. */
90const MaximumSecurityGuard *protocore_secure_guard(void);
91
92/**
93 * @brief @p n persistent plaintext bytes, zeroed, or NULL if the calling worker's cellblock is full.
94 *
95 * State that lasts across dispatches starts from zero, and a cellblock hands its cells back as
96 * they were, so this is the persistent borrow followed by the zeroing.
97 */
99
100/** @brief @p n persistent key-material bytes, zeroed, or NULL if the calling worker's cellblock is full. */
102
103// This header is also scheduling: starting, waking, stopping and deferring onto those workers.
104
105// ---------------------------------------------------------------------------
106// Worker tasks
107// ---------------------------------------------------------------------------
108//
109// Where the platform has a scheduler the server runs in dedicated worker tasks instead of the
110// user's loop(): protocore_workers_start() spawns PROTOCORE_WORKER_COUNT tasks, each
111// pinned to a core, each binding its worker id and repeatedly invoking the
112// app-supplied pump (so this layer stays free of any app dependency). On host
113// builds there are no tasks - the pipeline is driven inline by handle() / tests -
114// so these are no-ops and protocore_workers_running() is false.
115
116/** @brief Pump callback run by each worker task with its worker id. */
117typedef void (*protocore_worker_pump_fn)(int worker_id);
118
119// ---------------------------------------------------------------------------
120// Deferred work (thread-safe app -> worker submission)
121// ---------------------------------------------------------------------------
122//
123// HttpRoute a callback to a worker so it runs in that worker's single-thread context.
124// This is how application code on loop() (or any other task) safely pushes to a
125// connection - e.g. an SSE broadcast on a timer, or ws_send from a sensor task:
126// instead of calling the send API directly (which would race the worker that owns
127// the slot), wrap it in a small function and hand it to the owning worker. The
128// worker drains and runs deferred callbacks each service iteration.
129// (defer(slot, fn, arg) is the app-facing wrapper that resolves the
130// slot's owner; this layer stays free of the transport/conn_pool dependency.)
131//
132// @p arg must remain valid until the callback runs (point it at static/global
133// state, or data you keep alive). On host builds (no worker task) the callback
134// runs inline immediately, so tests and loop()-driven code behave identically.
135
136/** @brief Deferred callback signature. */
137typedef void (*protocore_deferred_fn)(void *arg);
138
139/** @brief One deferred call: what runs, and what it is given. */
140typedef struct
141{
142 protocore_deferred_fn fn; ///< what the worker runs
143 void *arg; ///< the opaque context it is given
145
146/**
147 * @brief The Workers module.
148 *
149 * A caller sets the members a call takes, invokes it through ::Workers, and reads the outcome off
150 * the same handle.
151 *
152 * @var WorkerNs::worker_id whose queue or task a call names
153 * @var WorkerNs::pump what each worker task runs each iteration
154 * @var WorkerNs::defer_args the call a defer hands to a worker; the arg must outlive it
155 * @var WorkerNs::ok a call's true/false outcome
156 * @var WorkerNs::run_deferred run every callback queued for worker_id, in its own context
157 * @var WorkerNs::running whether the worker tasks are up
158 * @var WorkerNs::start spawn the tasks and bind the pump
159 * @var WorkerNs::stop ask them to exit
160 * @var WorkerNs::wake nudge one so it services now rather than on the idle timeout
161 * @var WorkerNs::defer hand fn+arg to worker_id's queue and wake it
162 */
163typedef struct
164{
165 int worker_id; ///< the worker every call names
166 protocore_worker_pump_fn pump; ///< what a started worker runs each time it wakes
167 WorkerDeferArgs defer_args; ///< the call handed to a worker to run in its own context
169#if PROTOCORE_ENABLE_PREEMPT_QUEUE
170 // The lane the workers jump. They run without it; it only changes what runs first.
171 PreemptQueueNs *queue;
172#endif
174
175/** @brief The operands and the outcome. */
176extern WorkersVars WorkersV;
177
178/** @brief The entries. */
179typedef struct
180{
181 void (*const run_deferred)(uint8_t *work);
182 void (*const running)(uint8_t *work);
183 void (*const start)(uint8_t *work);
184 void (*const stop)(uint8_t *work);
185 void (*const wake)(uint8_t *work);
186 void (*const defer)(uint8_t *work);
187} WorkerNs;
188
189// What the table binds, defined once in the .c and taking one parameter each: everything
190// else an entry needs is an operand in WorkersV or a region of the borrow at a fixed offset.
192void protocore_workers_running(uint8_t *work);
193void protocore_workers_start(uint8_t *work);
194void protocore_workers_stop(uint8_t *work);
195void protocore_workers_wake(uint8_t *work);
196void protocore_workers_defer(uint8_t *work);
197#if PROTOCORE_ENABLE_PREEMPT_QUEUE
198#endif
199
200// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
201// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
202// `Workers.run_deferred(work)` resolves to a named function and becomes a DIRECT call. An extern table
203// leaves the call indirect and the symbol live at every level, -O2 -flto included.
204static const WorkerNs Workers __attribute__((unused)) = {
206 .running = protocore_workers_running,
211#if PROTOCORE_ENABLE_PREEMPT_QUEUE
212#endif
213};
214
215/**
216 * @brief The PROTOCORE_WORKER_BORROW bytes this module's state lives in.
217 *
218 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
219 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
220 * walks, so the state lasts the life of the program.
221 *
222 * @return the span.
223 */
225
227
228#endif // PROTOCORE_WORKER_H
#define PROTOCORE_INLINE
Linkage for a leaf primitive whose body is cheaper than the call that reaches it.
User-configurable preempting work queues + high-priority processing tasks (PROTOCORE_ENABLE_PREEMPT_Q...
One deferred call: what runs, and what it is given.
Definition worker.h:141
protocore_deferred_fn fn
what the worker runs
Definition worker.h:142
void * arg
the opaque context it is given
Definition worker.h:143
The entries.
Definition worker.h:180
void(*const run_deferred)(uint8_t *work)
Definition worker.h:181
proto_bool ok
Definition worker.h:168
WorkerDeferArgs defer_args
the call handed to a worker to run in its own context
Definition worker.h:167
protocore_worker_pump_fn pump
what a started worker runs each time it wakes
Definition worker.h:166
int worker_id
the worker every call names
Definition worker.h:165
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97
void(* protocore_deferred_fn)(void *arg)
Deferred callback signature.
Definition worker.h:137
void protocore_workers_stop(uint8_t *work)
void protocore_workers_defer(uint8_t *work)
const MinimumSecurityGuard * protocore_plain_guard(void)
The plaintext guard the calling worker borrows through. Never NULL.
PROTOCORE_BEGIN_DECLS int protocore_worker_count(void)
Number of server worker tasks (PROTOCORE_WORKER_COUNT).
void protocore_workers_run_deferred(uint8_t *work)
void protocore_workers_running(uint8_t *work)
void protocore_worker_set_self(int id)
Bind the calling task/thread to worker id id (worker entry / tests).
uint8_t * protocore_worker_span(void)
The PROTOCORE_WORKER_BORROW bytes this module's state lives in.
void protocore_workers_start(uint8_t *work)
void * protocore_secure_persist(size_t n)
n persistent key-material bytes, zeroed, or NULL if the calling worker's cellblock is full.
int protocore_worker_self(void)
Worker id [0, count) of the calling task; 0 by default / single-worker.
void * protocore_plain_persist(size_t n)
n persistent plaintext bytes, zeroed, or NULL if the calling worker's cellblock is full.
const MaximumSecurityGuard * protocore_secure_guard(void)
The key-material guard the calling worker borrows through. Never NULL.
WorkersVars WorkersV
The operands and the outcome.
void(* protocore_worker_pump_fn)(int worker_id)
Pump callback run by each worker task with its worker id.
Definition worker.h:117
void protocore_workers_wake(uint8_t *work)
void protocore_cellblocks_bind(int worker, const MinimumSecurityGuard *plain, const MaximumSecurityGuard *secure)
Hand worker slot worker the guards it borrows through.