DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
session.cpp
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 session.cpp
6 * @brief Layer 5 (Session) - event queue processor implementation.
7 *
8 * server_tick() is the only function here. Its bounded loop drains every
9 * active listener's FreeRTOS queue in one call so that the application layer
10 * always sees the most up-to-date state before checking http_pool[].
11 *
12 * Events are routed to the correct protocol handler via TcpConn::proto.
13 * A slot must carry an explicit protocol (assigned from its listener on
14 * accept); ConnProto::PROTO_NONE and any unregistered protocol resolve to no handler
15 * and the event is dropped.
16 */
17
18#include "session.h"
19#include "../transport/listener.h"
20#include "proto_handler.h"
21#include "scratch.h"
22
23// This layer is protocol-agnostic: it owns the dispatch mechanism only (register / look up /
24// route / drain) and names no protocol. Each protocol's handler lives in its own module and is
25// installed through proto_register_builtins() (proto_builtins.cpp, the policy list).
26
27// ---------------------------------------------------------------------------
28// Protocol-handler dispatch table (see proto_handler.h)
29// ---------------------------------------------------------------------------
30// Protocol-handler dispatch table, owned by one instance (internal linkage): the per-protocol
31// ProtoHandler pointers. One named owner, unreachable from any other translation unit.
36static SessionCtx s_session;
37
39{
40 if ((unsigned)proto < DETWS_PROTO_MAX)
41 s_session.proto_handlers[(unsigned)proto] = h;
42}
43
45{
46 // Install the built-ins on first lookup so dispatch works before begin() (the native test
47 // harness drives server_tick() directly). The list itself lives in proto_builtins.cpp -
48 // this dispatcher names no protocol; it just knows ConnProto::PROTO_HTTP is always registered, and
49 // uses that as the "already bootstrapped" sentinel.
50 if (!s_session.proto_handlers[(unsigned)ConnProto::PROTO_HTTP])
52 // No implicit fallback: a slot must carry an explicit, registered protocol.
53 // ConnProto::PROTO_NONE and any unregistered protocol resolve to nullptr (event dropped).
54 return ((unsigned)proto < DETWS_PROTO_MAX) ? s_session.proto_handlers[(unsigned)proto] : nullptr;
55}
56
57// Dispatch one drained event to its slot's protocol handler. Shared by the
58// single-queue (N=1) and per-worker-queue (N>1) drain paths below.
59static inline void dispatch_event(const TcpEvt &evt)
60{
61 // Per-dispatch reset of the calling worker's scratch arena: every handler
62 // runs with the whole arena available, and any scratch it borrows is
63 // reclaimed before the next event - the backstop that stops a forgotten
64 // release from accumulating across events.
66
67 // Route to the slot's protocol handler. ConnProto::PROTO_NONE and any unregistered
68 // protocol have no handler, so the event is dropped.
70 if (!h)
71 return;
72
73 switch (evt.type)
74 {
76 if (h->on_accept)
77 h->on_accept(evt.slot_id);
78 break;
80 if (h->on_data)
81 h->on_data(evt.slot_id);
82 break;
85 if (h->on_close)
86 h->on_close(evt.slot_id);
87 break;
88 }
89}
90
91void server_tick(int worker_id)
92{
93 /*
94 * Check timeouts BEFORE draining events. This ensures that a slot
95 * freed by a timeout is already in ConnState::CONN_FREE state if a coincident
96 * EvtType::EVT_DISCONNECT or EvtType::EVT_ERROR is dequeued in the same tick - the
97 * http_reset() call for that event is then a clean no-op. Each worker
98 * sweeps only the slots it owns.
99 */
101
102#if DETWS_WORKER_COUNT > 1
103 // Drain only this worker's queue: it is the sole consumer of its slots.
104 QueueHandle_t q = listener_worker_queue(worker_id);
105 if (!q)
106 return;
107 TcpEvt evt;
108 while (xQueueReceive(q, &evt, 0) == pdTRUE)
109 dispatch_event(evt);
110#else
111 (void)worker_id; // single worker owns all slots; drain every listener queue
112 for (uint8_t li = 0; li < MAX_LISTENERS; li++)
113 {
114 Listener *lst = &listener_pool[li];
115 if (!lst->active || !lst->queue)
116 continue;
117
118 TcpEvt evt;
119 while (xQueueReceive(lst->queue, &evt, 0) == pdTRUE)
120 dispatch_event(evt);
121 }
122#endif
123}
#define MAX_LISTENERS
Maximum number of simultaneously active listener ports.
ConnProto
Application protocol spoken on a listener port or connection slot.
@ PROTO_HTTP
HTTP/1.1 with optional WS and SSE upgrades.
#define DETWS_PROTO_MAX
Size of the protocol-handler dispatch table; must exceed the largest ConnProto id.
static void check_timeouts(int worker_id=0)
Scan the pool and force-close connections idle for > conn_timeout_ms.
Definition tcp.cpp:727
Listener listener_pool[MAX_LISTENERS]
Static pool of listener contexts. Defined in listener.cpp.
Definition listener.cpp:34
void proto_register_builtins(void)
Register every built-in protocol's handler (the policy list).
Layer 5 (Session) - per-protocol connection handler dispatch table.
void scratch_reset(void)
Reclaim the whole arena (empties it).
Definition scratch.cpp:110
Shared per-dispatch scratch arena (Layer 5, session-scoped memory).
void proto_register(ConnProto proto, const ProtoHandler *h)
Register h for protocol proto (replaces any previous handler).
Definition session.cpp:38
const ProtoHandler * proto_get(ConnProto proto)
Look up the handler for proto.
Definition session.cpp:44
void server_tick(int worker_id)
Drive the session layer for one Arduino loop iteration.
Definition session.cpp:91
Layer 5 (Session) - event queue dispatcher and session lifecycle.
State for one TCP listening port.
Definition listener.h:55
QueueHandle_t queue
Handle returned by xQueueCreateStatic().
Definition listener.h:61
bool active
True after listener_add(), false after listener_stop().
Definition listener.h:62
Per-protocol connection event/poll callbacks (Layer 5 dispatch vtable).
void(* on_close)(uint8_t slot)
EvtType::EVT_DISCONNECT / EvtType::EVT_ERROR: tear down slot state.
void(* on_data)(uint8_t slot)
EvtType::EVT_DATA: bytes are available in the slot's rx ring.
void(* on_accept)(uint8_t slot)
EvtType::EVT_CONNECT: a new connection was accepted.
const ProtoHandler * proto_handlers[DETWS_PROTO_MAX]
Definition session.cpp:34
ConnProto proto
Application protocol for this connection.
Definition tcp.h:87
Event record posted from lwIP callbacks to the session layer.
Definition tcp.h:149
EvtType type
What happened.
Definition tcp.h:150
uint8_t slot_id
Which connection slot is affected.
Definition tcp.h:151
TcpConn conn_pool[CONN_POOL_SLOTS]
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots pl...
Definition tcp.cpp:347
@ EVT_DATA
Data received; bytes are already in the ring buffer.
@ EVT_CONNECT
New connection accepted.
@ EVT_DISCONNECT
Remote peer closed the connection gracefully.
@ EVT_ERROR
lwIP reported an error (PCB may already be freed).