libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_slab.h
1/*-
2 * Copyright (c) 2026, The XTC Project
3 * Use of this source code is governed by the ISC License.
4 *
5 * src/inc/xtc_slab.h
6 * libumem-inspired slab allocator with per-loop magazines.
7 * Foundation for fixed-size hot-path allocations across xtc.
8 * Replaces ad-hoc per-module pools (lockmgr entries, proc
9 * mailbox envelopes, channel slots, RCU retired nodes, timer
10 * nodes).
11 *
12 * Design:
13 * - Each cache owns one or more "chunks" (large mmap regions),
14 * carved into per-object slots. No per-object malloc.
15 * - Chunks are sub-divided into "slabs" (default 64 KiB each)
16 * tracked on free/partial/full lists.
17 * - Each loop has a magazine -- a small array of recently-freed
18 * object pointers -- for lock-free fast-path alloc/free.
19 * On magazine miss, the cache mutex is taken and the magazine
20 * is refilled from a partial slab.
21 * - On magazine overflow (free), the magazine spills back to
22 * the cache's free list under the mutex.
23 * - Constructor runs once per object lifetime (when first
24 * pulled out of the slab); destructor runs once at cache
25 * destroy or at slab reap.
26 * - Magazines flush on memory pressure; full slabs return to
27 * the OS via munmap.
28 *
29 * Two storage modes:
30 * PROCESS_LOCAL -- chunks via mmap MAP_ANONYMOUS; objects are
31 * process-local pointers.
32 * SHARED_MEMORY -- chunks carved from a single user-supplied
33 * region (mmap MAP_SHARED of a tmpfs/posix-shm fd). Object
34 * handles are offsets (xtc_slab_off_t, a base-relative
35 * offset) so a peer process mapping the same fd at a
36 * different VA can resolve them. Use xtc_slab_resolve() and
37 * xtc_slab_offset() to convert.
38 *
39 * Cross-process support: verified working. Multiple processes
40 * can attach to the same shm region (via shm_open + mmap) and
41 * allocate AND free concurrently without collision. The region
42 * contains a 64-byte header at offset 0 holding an atomic cursor
43 * (carves fresh slots) and a cross-process free list threaded
44 * through region-relative offsets (reclaims freed slots), so a
45 * slot freed by any attacher is reusable by any other -- memory
46 * is not bump-only and does not leak until destroy. First
47 * attacher initializes the header; subsequent attachers verify
48 * magic and the slot stride, then share the cursor and free list.
49 * NOTE: the shm header (the first 64 bytes) is private to the
50 * allocator; do not stash inter-process scratch there.
51 *
52 * Typical usage:
53 * 1. Process A: shm_open + ftruncate + mmap(MAP_SHARED)
54 * 2. Process A: xtc_slab_create with shm_base/shm_size
55 * 3. Process B: shm_open + mmap(MAP_SHARED) same path
56 * 4. Process B: xtc_slab_create with same shm_base/shm_size
57 * 5. Either process can now alloc; the other can resolve
58 * the offset to access the object.
59 *
60 * Debug flags (M11.5a):
61 * REDZONE -- 16-byte guard before+after each object; checked
62 * on free
63 * AUDIT -- small ring of recent (alloc, free, who, when)
64 * events per cache for postmortem
65 * BACKTRACE -- capture backtrace on alloc; combine with AUDIT
66 * to identify leakers (via the __os_backtrace seam;
67 * a no-op where no backend is available)
68 *
69 * OOM policies:
70 * FAIL -- return NULL, set XTC_E_RESOURCE in caller
71 * BACKOFF -- short usleep + retry once, then FAIL
72 * ABORT -- call abort() (debug builds only; never in prod)
73 *
74 * Memory-pressure: callers can register `xtc_slab_pressure_listen`
75 * on Linux (/proc/pressure/memory PSI). When pressure is high,
76 * all caches drop their magazines and reap empty slabs.
77 */
78
79#ifndef XTC_SLAB_H
80#define XTC_SLAB_H
81
82#include <stddef.h>
83#include <stdint.h>
84
85#include "xtc.h"
86#include "xtc_res.h"
87#include "xtc_proc.h"
88#include "xtc_loop.h"
89
90typedef struct xtc_slab xtc_slab_t;
91
92/* Offset handle for shared-memory caches (a base-relative offset). */
93typedef int64_t xtc_slab_off_t;
94#define XTC_SLAB_OFF_NONE ((xtc_slab_off_t)-1)
95
96/* Per-object lifecycle hooks. */
97typedef int (*xtc_slab_ctor_fn)(void *obj, void *user);
98typedef void (*xtc_slab_dtor_fn)(void *obj, void *user);
99
100/* Memory-pressure callback signature. level: 0=normal 1=warning
101 * 2=critical. Caller may reap, drop caches, etc. */
102typedef void (*xtc_slab_pressure_fn)(int level, void *user);
103
104/* Opaque handle for a stoppable pressure listener (see
105 * xtc_slab_pressure_listen_ex / xtc_slab_pressure_stop). */
106typedef struct psi_listener xtc_slab_pressure_t;
107
108/* Storage mode. */
109typedef enum xtc_slab_mode {
110 XTC_SLAB_PROCESS_LOCAL = 0,
111 XTC_SLAB_SHARED_MEMORY = 1
112} xtc_slab_mode_t;
113
114/* OOM policy. */
115typedef enum xtc_slab_oom_policy {
116 XTC_SLAB_OOM_FAIL = 0, /* return NULL, increment rejects */
117 XTC_SLAB_OOM_BACKOFF = 1, /* brief usleep + retry once */
118 XTC_SLAB_OOM_ABORT = 2 /* call abort() (debug builds) */
119} xtc_slab_oom_policy_t;
120
121/* Debug flags (bit OR'd into opts.flags). */
122#define XTC_SLAB_REDZONE (1u << 0)
123#define XTC_SLAB_AUDIT (1u << 1)
124#define XTC_SLAB_BACKTRACE (1u << 2)
125#define XTC_SLAB_NO_MAGAZINE (1u << 3) /* always go through cache lock */
126
127typedef struct xtc_slab_opts {
128 const char *name; /* required, for stats/diag */
129 size_t obj_size; /* required */
130 size_t align; /* default cache-line, 64 */
131 size_t chunk_size; /* mmap unit; default 64 KiB */
132 int magazine_size; /* per-loop cache; default 16 */
133 xtc_slab_ctor_fn ctor; /* optional */
134 xtc_slab_dtor_fn dtor; /* optional */
135 void *cb_user;
136 unsigned flags;
137 xtc_slab_oom_policy_t oom_policy;
138 xtc_res_t *res; /* optional accounting */
139
140 /* Shared-memory mode only: caller-supplied base + size of an
141 * already-mapped region. When set, the cache carves chunks
142 * from this region instead of mmap'ing fresh ones. Released
143 * via shm_unlink when the cache is destroyed. */
144 xtc_slab_mode_t mode;
145 void *shm_base;
146 size_t shm_size;
148
149#define XTC_SLAB_OPTS_DEFAULT { \
150 .name = "anon", \
151 .obj_size = 0, \
152 .align = 64, \
153 .chunk_size = 64 * 1024, \
154 .magazine_size = 16, \
155 .ctor = NULL, .dtor = NULL, .cb_user = NULL, \
156 .flags = 0, \
157 .oom_policy = XTC_SLAB_OOM_FAIL, \
158 .res = NULL, \
159 .mode = XTC_SLAB_PROCESS_LOCAL, \
160 .shm_base = NULL, \
161 .shm_size = 0 \
162}
163
164typedef struct xtc_slab_stats {
165 uint64_t alloc_fast; /* magazine hit */
166 uint64_t alloc_slow; /* magazine miss -> cache lock */
167 uint64_t free_fast;
168 uint64_t free_slow;
169 uint64_t n_inuse;
170 uint64_t n_free; /* available in slabs */
171 uint64_t n_chunks;
172 uint64_t bytes_inuse;
173 uint64_t bytes_total;
174 uint64_t reaps;
175 uint64_t oom_fails;
176 uint64_t redzone_violations;
178
179/*
180 * PUBLIC: int xtc_slab_create __P((const xtc_slab_opts_t *, xtc_slab_t **));
181 * PUBLIC: void xtc_slab_destroy __P((xtc_slab_t *));
182 *
183 * PUBLIC: void *xtc_slab_alloc __P((xtc_slab_t *));
184 * PUBLIC: void xtc_slab_free __P((xtc_slab_t *, void *));
185 *
186 * PUBLIC: int xtc_slab_reap __P((xtc_slab_t *));
187 * PUBLIC: int xtc_slab_stat __P((const xtc_slab_t *, xtc_slab_stats_t *));
188 *
189 * PUBLIC: xtc_slab_off_t xtc_slab_offset __P((const xtc_slab_t *, const void *));
190 * PUBLIC: void *xtc_slab_resolve __P((const xtc_slab_t *, xtc_slab_off_t));
191 *
192 * PUBLIC: int xtc_slab_pressure_listen __P((const char *, xtc_slab_pressure_fn, void *));
193 * PUBLIC: int xtc_slab_pressure_listen_ex __P((const char *, xtc_slab_pressure_fn, void *, xtc_slab_pressure_t **));
194 * PUBLIC: int xtc_slab_pressure_stop __P((xtc_slab_pressure_t *));
195 * PUBLIC: int xtc_slab_reap_all __P((void));
196 */
197
198int xtc_slab_create(const xtc_slab_opts_t *opts, xtc_slab_t **out);
199void xtc_slab_destroy(xtc_slab_t *slab);
200
201void *xtc_slab_alloc(xtc_slab_t *slab);
202void xtc_slab_free(xtc_slab_t *slab, void *obj);
203
204/* Drop all magazines + return empty slabs to the OS. */
205int xtc_slab_reap(xtc_slab_t *slab);
206
207int xtc_slab_stat(const xtc_slab_t *slab, xtc_slab_stats_t *out);
208
209/* Shared-memory helpers -- convert between process-local pointer
210 * and a stable offset. In PROCESS_LOCAL mode these are still
211 * meaningful but only valid within this process. */
212xtc_slab_off_t xtc_slab_offset(const xtc_slab_t *slab, const void *p);
213void *xtc_slab_resolve(const xtc_slab_t *slab, xtc_slab_off_t off);
214
215/* Install a process-wide memory-pressure listener. On Linux, opens
216 * /proc/pressure/memory in poll mode at the "some" trigger (>10 ms
217 * stall in 1s); on platforms without PSI, returns XTC_E_NOSYS. When
218 * pressure fires, all registered slab caches receive a reap call.
219 * `psi_path` is "/proc/pressure/memory" by default (NULL = use it).
220 */
221int xtc_slab_pressure_listen(const char *psi_path,
222 xtc_slab_pressure_fn fn,
223 void *user);
224
225/* Like xtc_slab_pressure_listen, but returns an opaque handle so the
226 * listener can be stopped (its background thread joined and its fds
227 * closed) with xtc_slab_pressure_stop. On platforms without PSI,
228 * returns XTC_E_NOSYS and leaves *out NULL. *out must be non-NULL. */
229int xtc_slab_pressure_listen_ex(const char *psi_path,
230 xtc_slab_pressure_fn fn,
231 void *user,
232 xtc_slab_pressure_t **out);
233
234/* Stop a listener created by xtc_slab_pressure_listen_ex: signal its
235 * thread, join it, close its fds, and free the handle. Idempotent on
236 * NULL (returns XTC_E_INVAL). After this returns the handle is
237 * invalid. */
238int xtc_slab_pressure_stop(xtc_slab_pressure_t *handle);
239
240/* Fan a reap across every cache currently registered. Returns the
241 * total number of objects reaped. */
242int xtc_slab_reap_all(void);
243
244/* Spawn an xtc_proc that calls xtc_slab_reap_all() every
245 * `interval_ns` and logs the count via xtc_log. The proc runs
246 * forever; supervise it or rely on loop teardown to kill it.
247 * Returns the proc's pid (XTC_PID_NONE on failure). */
248int xtc_slab_reaper_spawn(xtc_loop_t *loop, int64_t interval_ns,
249 xtc_pid_t *out_pid);
250
251#endif /* XTC_SLAB_H */