ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
sockpool.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 sockpool.h
6 * @brief Dynamic socket recycling: an LRU connection-slot pool (PROTOCORE_ENABLE_SOCKPOOL).
7 *
8 * A device serves a bounded number of concurrent connections. When the pool saturates, the right move is
9 * not to drop the new connection but to *recycle* the least-recently-active slot (the one most likely to
10 * be a dead / idle keep-alive) and hand it to the new peer, returning the evicted id so the transport can
11 * close it cleanly. This is the transport-pool half left open by `services/netadapt`.
12 *
13 * This is that pure policy: a fixed table of connection slots (each an id + last-used tick), with acquire
14 * (free slot, else LRU-recycle), touch (mark active), release, and find. The app owns the real sockets;
15 * this owns *which* slot a connection lives in and which to reclaim under pressure. No heap, no stdlib,
16 * host-testable.
17 */
18
19#ifndef PROTOCORE_SOCKPOOL_H
20#define PROTOCORE_SOCKPOOL_H
21
22#include "protocore_config.h" // the entry point: protocore_types.h for the widths
23
24#if PROTOCORE_ENABLE_SOCKPOOL
25
27
28// This module holds nothing between calls, so it carves no borrow and states none. An entry
29// takes one all the same, and never reads it, so every namespace in the tree is invoked the
30// same way.
31
32/** @brief One connection slot. */
33typedef struct
34{
35 proto_bool in_use;
36 uint32_t id; ///< application connection id (e.g. a socket fd / handle).
37 uint32_t last_used; ///< tick of the last acquire/touch (for LRU).
38} SockSlot;
39
40/** @brief A fixed pool of connection slots (storage is caller-owned). */
41typedef struct
42{
43 SockSlot *slots;
44 size_t n;
45} SockPool;
46
47/** @brief Acquire outcome (the sole return of protocore_sockpool_acquire). */
48typedef enum PROTO_ENUM_PACKED
49{
50 SOCK_ACQ_FREE = 0, ///< a free slot was used.
51 SOCK_ACQ_RECYCLED = 1, ///< the pool was full; the LRU slot was recycled (see evicted_id).
52 SOCK_ACQ_FAIL = 2 ///< the pool has zero slots / bad args.
53} SockAcq;
54
55/** @brief What init takes: p, slots, n. */
56typedef struct
57{
58 SockPool *p;
59 SockSlot *slots;
60 size_t n;
61} SockpoolInitArgs;
62
63/** @brief What acquire takes: p, id, now, idx, evicted_id. */
64typedef struct
65{
66 SockPool *p;
67 uint32_t id;
68 uint32_t now;
69 size_t *idx; ///< (may be null) receives the chosen slot index
70 uint32_t *evicted_id;
71} SockpoolAcquireArgs;
72
73/** @brief What touch takes: p, idx, now. */
74typedef struct
75{
76 SockPool *p;
77 size_t idx;
78 uint32_t now;
79} SockpoolTouchArgs;
80
81/** @brief What release takes: p, idx. */
82typedef struct
83{
84 SockPool *p;
85 size_t idx;
86} SockpoolReleaseArgs;
87
88/** @brief What find takes: p, id, idx. */
89typedef struct
90{
91 const SockPool *p;
92 uint32_t id;
93 size_t *idx;
94} SockpoolFindArgs;
95
96/** @brief What in_use takes: p. */
97typedef struct
98{
99 const SockPool *p;
100} SockpoolInUseArgs;
101
102/**
103 * @brief Dynamic socket recycling: an LRU connection-slot pool (PROTOCORE_ENABLE_SOCKPOOL). A device serves a bounded
104 * ...
105 *
106 * A caller sets the members a call takes, invokes it through ::Sockpool with the bytes it runs
107 * out of, and reads the outcome off the same handle.
108 *
109 * Sockpool.init_args.p = ...;
110 * Sockpool.init_args.slots = ...;
111 * Sockpool.init_args.n = ...;
112 * Sockpool.init(work);
113 *
114 * @var SockpoolNs::init_args what init takes: p, slots, n
115 * @var SockpoolNs::acquire_args what acquire takes: p, id, now, idx, evicted_id
116 * @var SockpoolNs::touch_args what touch takes: p, idx, now
117 * @var SockpoolNs::release_args what release takes: p, idx
118 * @var SockpoolNs::find_args what find takes: p, id, idx
119 * @var SockpoolNs::in_use_args what in_use takes: p
120 * @var SockpoolNs::ok a call's true/false outcome
121 * @var SockpoolNs::acq SOCK_ACQ_FREE / SOCK_ACQ_RECYCLED / SOCK_ACQ_FAIL
122 * @var SockpoolNs::n the count a call reports
123 * @var SockpoolNs::init initialize a pool over caller storage; all slots start free
124 * @var SockpoolNs::acquire acquire a slot for connection id at tick now. Uses a free slot if ...
125 * @var SockpoolNs::touch mark slot idx active at tick now (refreshes its LRU position)
126 * @var SockpoolNs::release free slot idx. true if it was a valid, in-use slot
127 * @var SockpoolNs::find find the slot holding connection id. idx (may be null) gets the ...
128 * @var SockpoolNs::in_use count of in-use slots
129 *
130 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
131 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
132 * a caller drives every namespace the same way.
133 */
134typedef struct
135{
136 SockpoolInitArgs init_args;
137 SockpoolAcquireArgs acquire_args;
138 SockpoolTouchArgs touch_args;
139 SockpoolReleaseArgs release_args;
140 SockpoolFindArgs find_args;
141 SockpoolInUseArgs in_use_args;
142 proto_bool ok;
143 SockAcq acq;
144 size_t n;
145} SockpoolVars;
146
147/** @brief The operands and the outcome. */
148extern SockpoolVars SockpoolV;
149
150/** @brief The entries. */
151typedef struct
152{
153 void (*const init)(uint8_t *work);
154 void (*const acquire)(uint8_t *work);
155 void (*const touch)(uint8_t *work);
156 void (*const release)(uint8_t *work);
157 void (*const find)(uint8_t *work);
158 void (*const in_use)(uint8_t *work);
159} SockpoolNs;
160
161// What the table binds, defined once in the .c and taking one parameter each: everything
162// else an entry needs is an operand in SockpoolV or a region of the borrow at a fixed offset.
163void protocore_sockpool_init(uint8_t *work);
164void protocore_sockpool_acquire(uint8_t *work);
165void protocore_sockpool_touch(uint8_t *work);
166void protocore_sockpool_release(uint8_t *work);
167void protocore_sockpool_find(uint8_t *work);
168void protocore_sockpool_in_use(uint8_t *work);
169
170// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
171// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
172// `Sockpool.init(work)` resolves to a named function and becomes a DIRECT call. An extern table
173// leaves the call indirect and the symbol live at every level, -O2 -flto included.
174static const SockpoolNs Sockpool __attribute__((unused)) = {
175 .init = protocore_sockpool_init,
176 .acquire = protocore_sockpool_acquire,
177 .touch = protocore_sockpool_touch,
178 .release = protocore_sockpool_release,
179 .find = protocore_sockpool_find,
180 .in_use = protocore_sockpool_in_use,
181};
182
184
185#endif // PROTOCORE_ENABLE_SOCKPOOL
186
187#endif // PROTOCORE_SOCKPOOL_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#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