DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
scratch.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 scratch.h
6 * @brief Shared per-dispatch scratch arena (Layer 5, session-scoped memory).
7 *
8 * One fixed BSS arena that codec / protocol handlers borrow transient working
9 * memory from, instead of each feature carrying its own dedicated scratch
10 * buffer. Many such buffers are mutually exclusive in time - a connection is
11 * doing HTTP *or* WebSocket *or* SSH at any instant, and the session layer runs
12 * one event to completion before the next - so overlapping them in a single
13 * arena cuts peak DRAM without weakening the zero-heap / deterministic
14 * guarantee (fixed size, no runtime growth).
15 *
16 * **Model - region reset per dispatch.** scratch_alloc() bump-allocates from the
17 * arena; scratch_reset() empties it in O(1). server_tick() calls scratch_reset()
18 * at the top of every event dispatch, so a borrow is valid only until the
19 * handler returns. There is no per-allocation free - the whole arena is
20 * reclaimed at once.
21 *
22 * **Race-safety.** All codec / protocol logic runs only in the single loop task
23 * (server_tick() / handle()); the lwIP callbacks run in tcpip_thread and only
24 * fill the rx ring + enqueue events - they never borrow scratch. The arena
25 * therefore has exactly one accessor and needs no lock. In debug builds an
26 * owner-task assertion (xTaskGetCurrentTaskHandle) fails loud the instant any
27 * other context calls in, turning a future mistake into an immediate visible
28 * failure instead of a silent cross-core race.
29 *
30 * **Exhaustion-safety.** Borrows live only within one dispatch and are
31 * auto-reclaimed by the reset, so a forgotten free cannot accumulate (no
32 * creeping exhaustion). An over-budget scratch_alloc() returns nullptr; every
33 * caller must take a defined fail-closed path (drop the optional optimization,
34 * close the connection, answer 503) and must never dereference a null borrow.
35 *
36 * **No implicit zeroing.** scratch_alloc() returns uninitialized memory and the
37 * reset does not wipe; a security-sensitive tenant must wipe its own region
38 * after use (as the SSH crypto scratch already does).
39 *
40 * @author Douglas Quigg (dstroy0)
41 * @date 2026
42 */
43
44#ifndef DETERMINISTICESPASYNCWEBSERVER_SCRATCH_H
45#define DETERMINISTICESPASYNCWEBSERVER_SCRATCH_H
46
47#include "ServerConfig.h"
48#include <stddef.h>
49
50/**
51 * @brief Borrow @p n bytes of scratch, aligned to @p align.
52 *
53 * The returned pointer is valid only until the next scratch_reset() (i.e. only
54 * within the current session dispatch). Returns nullptr if the request does not
55 * fit the remaining arena - callers MUST handle null and fail closed.
56 *
57 * @param n bytes requested (0 yields a valid non-null pointer when space
58 * remains).
59 * @param align required alignment in bytes, a power of two (0 selects the
60 * platform default).
61 * @return pointer to @p n writable bytes, or nullptr if it does not fit.
62 */
63void *scratch_alloc(size_t n, size_t align);
64
65/**
66 * @brief Reclaim the whole arena (empties it).
67 *
68 * Called by server_tick() before each event dispatch. Invalidates every pointer
69 * previously returned by scratch_alloc().
70 */
71void scratch_reset(void);
72
73/**
74 * @brief Capture the current arena offset (a savepoint for scratch_release()).
75 * @return an opaque mark to pass to scratch_release().
76 */
77size_t scratch_mark(void);
78
79/**
80 * @brief Reclaim everything allocated since @p mark (LIFO).
81 *
82 * Restores the arena to a previous scratch_mark(), freeing every scratch_alloc()
83 * made in between. Marks must be released in reverse order (nested scopes). Use
84 * ScratchScope for return-safe scoping.
85 *
86 * @param mark a value previously returned by scratch_mark() (must be <= the
87 * current offset).
88 */
89void scratch_release(size_t mark);
90
91/** @brief Bytes currently handed out (0 immediately after a reset). */
92size_t scratch_used(void);
93
94/** @brief Largest scratch_used() value seen since boot (for sizing the arena). */
95size_t scratch_high_water(void);
96
97/** @brief Total arena capacity in bytes (DETWS_SCRATCH_ARENA_SIZE). */
98size_t scratch_capacity(void);
99
100/**
101 * @brief RAII scope guard for transient scratch borrows.
102 *
103 * Marks the arena on construction and restores it on destruction, so every
104 * scratch_alloc() made within the scope is reclaimed on *every* exit path
105 * (return, break, fall-through) - the safe way to borrow transient scratch in a
106 * function with multiple returns. Scopes must nest (LIFO); the per-dispatch
107 * scratch_reset() is the backstop if one is ever skipped.
108 */
110{
111 public:
113 {
114 }
116 {
117 scratch_release(m_mark);
118 }
119 ScratchScope(const ScratchScope &) = delete;
121
122 private:
123 size_t m_mark;
124};
125
126#endif // DETERMINISTICESPASYNCWEBSERVER_SCRATCH_H
User-facing configuration for DeterministicESPAsyncWebServer.
RAII scope guard for transient scratch borrows.
Definition scratch.h:110
ScratchScope(const ScratchScope &)=delete
ScratchScope & operator=(const ScratchScope &)=delete
size_t scratch_mark(void)
Capture the current arena offset (a savepoint for scratch_release()).
Definition scratch.cpp:117
size_t scratch_capacity(void)
Total arena capacity in bytes (DETWS_SCRATCH_ARENA_SIZE).
Definition scratch.cpp:147
void * scratch_alloc(size_t n, size_t align)
Borrow n bytes of scratch, aligned to align.
Definition scratch.cpp:85
size_t scratch_used(void)
Bytes currently handed out (0 immediately after a reset).
Definition scratch.cpp:132
size_t scratch_high_water(void)
Largest scratch_used() value seen since boot (for sizing the arena).
Definition scratch.cpp:137
void scratch_reset(void)
Reclaim the whole arena (empties it).
Definition scratch.cpp:110
void scratch_release(size_t mark)
Reclaim everything allocated since mark (LIFO).
Definition scratch.cpp:124