DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
preempt_queue.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 preempt_queue.h
6 * @brief User-configurable preempting work queues + high-priority processing tasks
7 * (DETWS_ENABLE_PREEMPT_QUEUE) - the v5 real-time ingest primitive.
8 *
9 * Fixed-capacity queues, each feeding one dedicated, core-pinned task. A producer posts
10 * a fixed-size item; the scheduler **preempts** the lower-priority producer the instant
11 * the item lands so it is processed immediately instead of on the next tick. Producers
12 * post from a task (back or front, with a wait timeout) or from an ISR (interrupt-safe,
13 * with an immediate context-switch request). Each processing task pops items in order
14 * and hands each to a user handler.
15 *
16 * **Named lanes.** There are several queues, addressed by @ref detws_pq_lane:
17 * - `detws_pq_lane::DETWS_PQ_LANE_USER` - the single lane exposed to the application. The no-lane
18 * `detws_pq_*` API drives it (so existing user code is unchanged). Lowest priority.
19 * - `detws_pq_lane::DETWS_PQ_LANE_DMA` / `_FORWARD` / `_DEVICE` - internal lanes for the library's own
20 * real-time work (DMA peripheral transfers, interface forwarding, device access).
21 * They run **above** the user lane (base `DETWS_PQ_INTERNAL_PRIORITY`, DMA highest),
22 * so internal ingest always preempts user work; and below the lwIP tcpip / WiFi tasks
23 * so networking is never starved.
24 *
25 * This is the single normalized pipe for "hardware event -> process now": a DMA-complete
26 * / GPIO / bus ISR posts a descriptor onto its lane, the lane's task drains it. Zero-heap
27 * queue storage (static, compile-time DETWS_PQ_DEPTH x DETWS_PQ_ITEM_SIZE per lane; a
28 * task's stack is created only when its lane starts, so unused lanes cost only their queue
29 * storage), fail-closed on a full queue, no hot-path locks - so latency stays bounded.
30 *
31 * Host builds have no FreeRTOS task: posts enqueue into the same fixed per-lane ring and
32 * detws_pq_drain[_lane]() runs the handler over what is queued, so the logic is
33 * host-testable and behaves identically to the device's draining task.
34 *
35 * @author Douglas Quigg (dstroy0)
36 * @date 2026
37 */
38
39#ifndef DETERMINISTICESPASYNCWEBSERVER_PREEMPT_QUEUE_H
40#define DETERMINISTICESPASYNCWEBSERVER_PREEMPT_QUEUE_H
41
42#include "ServerConfig.h"
43
44#if DETWS_ENABLE_PREEMPT_QUEUE
45
46#include <stddef.h>
47#include <stdint.h>
48
49/**
50 * @brief The preempting lanes, ordered by role. The USER lane is exposed to the
51 * application; the internal lanes run at a higher priority (DMA highest) so
52 * internal ingest preempts user work.
53 */
54enum class detws_pq_lane : uint8_t
55{
56 DETWS_PQ_LANE_USER = 0, ///< exposed to the app (no-lane API); lowest priority
57 DETWS_PQ_LANE_DMA, ///< internal: DMA peripheral transfers (highest)
58 DETWS_PQ_LANE_FORWARD, ///< internal: interface forwarding
59 DETWS_PQ_LANE_DEVICE, ///< internal: device access
60 DETWS_PQ_LANE_COUNT
61};
62
63/**
64 * @brief Handler a lane's processing task invokes for each dequeued item.
65 * @param item pointer to DETWS_PQ_ITEM_SIZE bytes (the posted item).
66 * @param ctx the opaque pointer passed to detws_pq_start[_lane]().
67 */
68typedef void (*DetwsPqHandler)(const void *item, void *ctx);
69
70/** @brief Processing-task configuration set by the caller at start. */
71struct DetwsPqConfig
72{
73 DetwsPqHandler handler; ///< Called once per dequeued item (required).
74 void *ctx; ///< Opaque, forwarded to @ref handler.
75 uint8_t priority; ///< Task priority; 0 = use the lane's default (internal > user).
76 uint8_t core; ///< Core to pin the task to (ESP32; ignored on host).
77 const char *name; ///< Task name (debug); may be nullptr.
78};
79
80// --- Lane API -------------------------------------------------------------------------
81
82/**
83 * @brief Create a lane's queue and start its processing task.
84 *
85 * Idempotent per lane: a second call while that lane runs is a no-op returning false. On
86 * host no task is started (drive with detws_pq_drain_lane()). If `cfg->priority` is 0 the
87 * lane's default priority is used (internal lanes default above the user lane).
88 * @return true if started; false on a bad lane / null handler / already running.
89 */
90bool detws_pq_start_lane(detws_pq_lane lane, const DetwsPqConfig *cfg);
91
92/** @brief Post to the back of a lane from a task context (copies DETWS_PQ_ITEM_SIZE
93 * bytes, blocks up to @p timeout_ticks, then fails closed). */
94bool detws_pq_post_lane(detws_pq_lane lane, const void *item, uint32_t timeout_ticks);
95
96/** @brief Post to the FRONT of a lane (urgent) from a task context. */
97bool detws_pq_post_lane_urgent(detws_pq_lane lane, const void *item, uint32_t timeout_ticks);
98
99/** @brief Post to a lane from an ISR (interrupt-safe; requests an immediate switch). */
100bool detws_pq_post_lane_from_isr(detws_pq_lane lane, const void *item);
101
102/** @brief Run the handler over every currently-queued item on a lane (host / inline
103 * drive). No-op on ARDUINO, where the lane's task drains it. */
104void detws_pq_drain_lane(detws_pq_lane lane);
105
106/** @brief Stop a lane's processing task (no-op on host). */
107void detws_pq_stop_lane(detws_pq_lane lane);
108
109/** @brief True while a lane's processing task is running (always false on host). */
110bool detws_pq_running_lane(detws_pq_lane lane);
111
112/** @brief Peak items ever queued at once on a lane (for sizing DETWS_PQ_DEPTH). */
113size_t detws_pq_high_water_lane(detws_pq_lane lane);
114
115/** @brief The default task priority for a lane (internal lanes rank above the user lane;
116 * DMA highest). Used when a config passes priority 0. */
117uint8_t detws_pq_lane_priority(detws_pq_lane lane);
118
119// --- User-lane API (unchanged; drives detws_pq_lane::DETWS_PQ_LANE_USER) -----------------------------
120
121/** @brief Start the USER lane. @see detws_pq_start_lane. */
122inline bool detws_pq_start(const DetwsPqConfig *cfg)
123{
124 return detws_pq_start_lane(detws_pq_lane::DETWS_PQ_LANE_USER, cfg);
125}
126/** @brief Post to the back of the USER lane. */
127inline bool detws_pq_post(const void *item, uint32_t timeout_ticks)
128{
129 return detws_pq_post_lane(detws_pq_lane::DETWS_PQ_LANE_USER, item, timeout_ticks);
130}
131/** @brief Post to the front of the USER lane (urgent). */
132inline bool detws_pq_post_urgent(const void *item, uint32_t timeout_ticks)
133{
134 return detws_pq_post_lane_urgent(detws_pq_lane::DETWS_PQ_LANE_USER, item, timeout_ticks);
135}
136/** @brief Post to the USER lane from an ISR. */
137inline bool detws_pq_post_from_isr(const void *item)
138{
139 return detws_pq_post_lane_from_isr(detws_pq_lane::DETWS_PQ_LANE_USER, item);
140}
141/** @brief Drain the USER lane (host / inline drive). */
142inline void detws_pq_drain(void)
143{
144 detws_pq_drain_lane(detws_pq_lane::DETWS_PQ_LANE_USER);
145}
146/** @brief Stop the USER lane's task. */
147inline void detws_pq_stop(void)
148{
149 detws_pq_stop_lane(detws_pq_lane::DETWS_PQ_LANE_USER);
150}
151/** @brief True while the USER lane's task is running. */
152inline bool detws_pq_running(void)
153{
154 return detws_pq_running_lane(detws_pq_lane::DETWS_PQ_LANE_USER);
155}
156/** @brief Peak items ever queued on the USER lane. */
157inline size_t detws_pq_high_water(void)
158{
159 return detws_pq_high_water_lane(detws_pq_lane::DETWS_PQ_LANE_USER);
160}
161
162#endif // DETWS_ENABLE_PREEMPT_QUEUE
163
164#endif // DETERMINISTICESPASYNCWEBSERVER_PREEMPT_QUEUE_H
User-facing configuration for DeterministicESPAsyncWebServer.