ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
worker.h
Go to the documentation of this file.
1// 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 Layer 5 (Session) - server worker identity.
7 *
8 * The server pipeline runs in one or more dedicated worker tasks (see
9 * PC_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 * pc_worker_set_self(); any context that has not bound an id (the user's
16 * loop(), a unit test, the lwIP thread) reads 0, which is also the only valid id
17 * in the default single-worker build, so PC_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/** @brief Number of server worker tasks (PC_WORKER_COUNT). */
30int pc_worker_count(void);
31
32/**
33 * @brief Worker id [0, count) of the calling task; 0 by default / single-worker.
34 *
35 * With PC_WORKER_COUNT == 1 (the default) there is exactly one worker, so the answer is 0 by
36 * construction and this is an inline constant - no lookup, no call. That matters because every pool
37 * borrow asks: the multi-worker path reads a `thread_local`, which on FreeRTOS resolves through the
38 * task's TLS block rather than a register, and it was being paid on operations that are otherwise a
39 * single struct-field read.
40 */
41#if PC_WORKER_COUNT == 1
42inline int pc_worker_self(void)
43{
44 return 0;
45}
46#else
47int pc_worker_self(void);
48#endif
49
50/** @brief Bind the calling task/thread to worker id @p id (worker entry / tests). */
51void pc_worker_set_self(int id);
52
53// ---------------------------------------------------------------------------
54// Worker tasks (ESP32)
55// ---------------------------------------------------------------------------
56//
57// On ESP32 the server runs in dedicated FreeRTOS worker tasks instead of the
58// user's loop(): pc_workers_start() spawns PC_WORKER_COUNT tasks, each
59// pinned to a core, each binding its worker id and repeatedly invoking the
60// app-supplied pump (so this layer stays free of any app dependency). On host
61// builds there are no tasks - the pipeline is driven inline by handle() / tests -
62// so these are no-ops and pc_workers_running() is false.
63
64/** @brief Pump callback run by each worker task with its worker id. */
65typedef void (*pc_worker_pump_fn)(int worker_id);
66
67/** @brief Spawn the worker task(s) and start them running @p pump. No-op on host. */
69
70/**
71 * @brief Wake worker @p worker_id so it services a freshly-queued event now.
72 *
73 * Each worker blocks between service iterations (it no longer free-runs the poll),
74 * so a producer that posts to a worker's event/defer queue must nudge it. This is
75 * a FreeRTOS task notification (no allocation, task- and tcpip-thread safe); a
76 * nudge that lands between the worker's pump and its block is latched in the
77 * notification count, so the next block returns at once and no event is missed.
78 * No-op on host (no worker task; the pipeline runs inline). Safe to call with an
79 * out-of-range id or before the task exists.
80 */
81void pc_worker_wake(int worker_id);
82
83/** @brief Signal the worker task(s) to exit and wait briefly for them. No-op on host. */
84void pc_workers_stop(void);
85
86/** @brief True while worker task(s) are running (always false on host). */
87bool pc_workers_running(void);
88
89// ---------------------------------------------------------------------------
90// Deferred work (thread-safe app -> worker submission)
91// ---------------------------------------------------------------------------
92//
93// Route a callback to a worker so it runs in that worker's single-thread context.
94// This is how application code on loop() (or any other task) safely pushes to a
95// connection - e.g. an SSE broadcast on a timer, or ws_send from a sensor task:
96// instead of calling the send API directly (which would race the worker that owns
97// the slot), wrap it in a small function and hand it to the owning worker. The
98// worker drains and runs deferred callbacks each service iteration.
99// (PC::defer(slot, fn, arg) is the app-facing wrapper that resolves the
100// slot's owner; this layer stays free of the transport/conn_pool dependency.)
101//
102// @p arg must remain valid until the callback runs (point it at static/global
103// state, or data you keep alive). On host builds (no worker task) the callback
104// runs inline immediately, so tests and loop()-driven code behave identically.
105
106/** @brief Deferred callback signature. */
107typedef void (*pc_deferred_fn)(void *arg);
108
109/** @brief Run @p fn(@p arg) on worker @p worker_id. Returns false if the queue is full. */
110bool pc_defer(int worker_id, pc_deferred_fn fn, void *arg);
111
112/** @brief Drain and run worker @p worker_id's deferred callbacks (called by the worker). */
113void pc_worker_run_deferred(int worker_id);
114
115#endif // PROTOCORE_WORKER_H
User-facing configuration for ProtoCore.
void pc_worker_run_deferred(int worker_id)
Drain and run worker worker_id's deferred callbacks (called by the worker).
Definition worker.cpp:174
void pc_worker_wake(int worker_id)
Wake worker worker_id so it services a freshly-queued event now.
Definition worker.cpp:161
void(* pc_worker_pump_fn)(int worker_id)
Pump callback run by each worker task with its worker id.
Definition worker.h:65
void pc_workers_start(pc_worker_pump_fn pump)
Spawn the worker task(s) and start them running pump. No-op on host.
Definition worker.cpp:118
bool pc_workers_running(void)
True while worker task(s) are running (always false on host).
Definition worker.cpp:202
void pc_worker_set_self(int id)
Bind the calling task/thread to worker id id (worker entry / tests).
Definition worker.cpp:41
int pc_worker_count(void)
Number of server worker tasks (PC_WORKER_COUNT).
Definition worker.cpp:29
bool pc_defer(int worker_id, pc_deferred_fn fn, void *arg)
Run fn(arg) on worker worker_id. Returns false if the queue is full.
Definition worker.cpp:142
void pc_workers_stop(void)
Signal the worker task(s) to exit and wait briefly for them. No-op on host.
Definition worker.cpp:190
int pc_worker_self(void)
Worker id [0, count) of the calling task; 0 by default / single-worker.
Definition worker.cpp:35
void(* pc_deferred_fn)(void *arg)
Deferred callback signature.
Definition worker.h:107