libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_res.h
1/*-
2 * Copyright (c) 2026, The XTC Project
3 * @stability stable
4 * Use of this source code is governed by the ISC License.
5 *
6 * src/inc/xtc_res.h
7 * Resource governance. An xtc_res_t is a per-executor (or
8 * per-loop) accountant that tracks bounded resources: tasks,
9 * channels, channel slots, file descriptors, memory. Acquire is
10 * atomic and either succeeds (counter <= cap) or returns
11 * XTC_E_RESOURCE; release is unconditional.
12 *
13 * WHAT IS METERED -- exactly, nothing else is charged by libxtc:
14 * TASKS, INBOX_MSGS every task spawned on a loop (xtc_async,
15 * xtc_proc_spawn, ...) against that loop's own accountant
16 * (xtc_loop_res); cross-thread spawns also charge INBOX_MSGS.
17 * CHANNELS, CHAN_SLOTS channels created with a non-NULL res.
18 * MEM_BYTES xtc_slab chunks when xtc_slab_opts_t.res is set, and
19 * xtc_mctx chunks (header + payload) of a context attached
20 * with xtc_res_attach_mctx. NOT xtc_malloc, fiber stacks,
21 * proc/mailbox structures, or any other internal allocation.
22 * FDS descriptors returned by xtc_net_listen, _dial, _accept,
23 * _unix_listen, _unix_dial and _udp_socket while an
24 * accountant is attached with xtc_res_attach_net, released by
25 * xtc_net_close. NOT fds from a raw accept(2) (use
26 * xtc_net_accept for inbound connections), files, pipes,
27 * the I/O backend's own fds, or xproc control sockets.
28 * A cap on a kind nothing charges bounds nothing. Metering that
29 * needs an attach is off by default (no cost, no behavior change).
30 */
31
32#ifndef XTC_RES_H
33#define XTC_RES_H
34
35#include "xtc_export.h"
36
37#include <stdint.h>
38#include <stdatomic.h>
39
40#include "xtc.h"
41
42typedef enum xtc_res_kind {
43 XTC_RES_TASKS = 0, /* live xtc_task_t allocations */
44 XTC_RES_CHANNELS = 1, /* live xtc_chan_* objects */
45 XTC_RES_CHAN_SLOTS = 2, /* in-flight messages across all chans */
46 XTC_RES_FDS = 3, /* open fds attributable to xtc */
47 XTC_RES_MEM_BYTES = 4, /* bytes in xtc-tracked allocations */
48 XTC_RES_INBOX_MSGS = 5, /* cross-loop inbox messages in flight */
49
50 XTC_RES__COUNT
51} xtc_res_kind_t;
52
53/*
54 * Caps. Zero means "no cap" (unbounded; debug only). Defaults
55 * pick reasonable values for a 4-loop M5 executor on a workstation.
56 */
57typedef struct xtc_res_caps {
58 size_t size; /* sizeof(xtc_res_caps_t): XTC_RES_CAPS_DEFAULT */
59 int64_t tasks; /* default 100000 */
60 int64_t channels; /* default 4096 */
61 int64_t chan_slots; /* default 1000000 */
62 int64_t fds; /* default 65536 */
63 int64_t mem_bytes; /* default 1 GiB */
64 int64_t inbox_msgs; /* default 65536 (per loop, not global) */
66
67#define XTC_RES_CAPS_DEFAULT { \
68 .size = sizeof(xtc_res_caps_t), \
69 .tasks = 100000, \
70 .channels = 4096, \
71 .chan_slots = 1000000, \
72 .fds = 65536, \
73 .mem_bytes = 1024L * 1024 * 1024, \
74 .inbox_msgs = 65536 \
75}
76
77typedef struct xtc_res {
78 xtc_res_caps_t caps;
79 _Atomic int64_t used[XTC_RES__COUNT];
80 _Atomic int64_t high[XTC_RES__COUNT]; /* high-water mark for stats */
81 _Atomic int64_t rejects[XTC_RES__COUNT];/* count of XTC_E_RESOURCE returns */
82
83 /* High-water alert callback: fires once when used / cap crosses
84 * the threshold percent (e.g. 0.8 = 80%). Re-arms when used
85 * drops below the threshold so a second crossing fires again.
86 * Set via xtc_res_set_alert. */
87 double alert_pct[XTC_RES__COUNT];
88 _Atomic int alert_armed[XTC_RES__COUNT]; /* 1 = ready to fire */
89 void (*alert_fn)(xtc_res_kind_t k, int64_t used,
90 int64_t cap, void *user);
91 void *alert_user;
92} xtc_res_t;
93
94/*
95 * PUBLIC: int xtc_res_init __P((xtc_res_t *, const xtc_res_caps_t *));
96 * PUBLIC: int xtc_res_acquire __P((xtc_res_t *, xtc_res_kind_t, int64_t));
97 * PUBLIC: void xtc_res_release __P((xtc_res_t *, xtc_res_kind_t, int64_t));
98 * PUBLIC: int64_t xtc_res_used __P((const xtc_res_t *, xtc_res_kind_t));
99 * PUBLIC: int64_t xtc_res_high __P((const xtc_res_t *, xtc_res_kind_t));
100 * PUBLIC: int64_t xtc_res_rejects __P((const xtc_res_t *, xtc_res_kind_t));
101 * PUBLIC: void xtc_res_set_cap __P((xtc_res_t *, xtc_res_kind_t, int64_t));
102 * PUBLIC: int xtc_res_attach_mctx __P((xtc_res_t *, struct xtc_mctx *));
103 * PUBLIC: int xtc_res_attach_net __P((xtc_res_t *));
104 */
105XTC_API int xtc_res_init(xtc_res_t *r, const xtc_res_caps_t *caps);
106
107/*
108 * Try to charge `n` units of `kind` to `r`. Returns:
109 * XTC_OK on success
110 * XTC_E_RESOURCE if the request would exceed the cap
111 * XTC_E_INVAL on a bad kind / negative n / NULL r
112 *
113 * Atomic and lock-free.
114 */
115XTC_API int xtc_res_acquire(xtc_res_t *r, xtc_res_kind_t k, int64_t n);
116
117/*
118 * Release `n` units. Never fails; clamps at zero on underflow
119 * (treated as a programming error in debug builds).
120 */
121XTC_API void xtc_res_release(xtc_res_t *r, xtc_res_kind_t k, int64_t n);
122
123XTC_API int64_t xtc_res_used(const xtc_res_t *r, xtc_res_kind_t k);
124XTC_API int64_t xtc_res_high(const xtc_res_t *r, xtc_res_kind_t k);
125XTC_API int64_t xtc_res_rejects(const xtc_res_t *r, xtc_res_kind_t k);
126
127/* Set one kind's cap at runtime; 0 = no cap. Returns nothing, so an
128 * invalid argument (NULL r, bad kind, NEGATIVE cap) is ignored and the
129 * existing cap is kept. Releases before 1.50 stored a negative cap,
130 * which then read as "no cap" -- silently unbounded. (A negative cap
131 * passed in the caps struct to xtc_res_init still means no cap.) */
132XTC_API void xtc_res_set_cap(xtc_res_t *r, xtc_res_kind_t k, int64_t cap);
133
134/* Configure a high-water alert. Fires `fn(kind, used, cap, user)`
135 * once when `used >= pct * cap` for the named resource; re-arms
136 * when used drops below. pct must lie in the OPEN interval
137 * (0.0, 1.0); 0, 1.0, anything outside, or NaN is XTC_E_INVAL
138 * (releases before 1.50 accepted 0 and 1.0). Pass fn=NULL to
139 * disable. Per-resource: alerts are independent.
140 *
141 * PUBLIC: int xtc_res_set_alert __P((xtc_res_t *, xtc_res_kind_t, double));
142 * PUBLIC: int xtc_res_set_alert_fn __P((xtc_res_t *, void (*)(xtc_res_kind_t, int64_t, int64_t, void *), void *));
143 */
144XTC_API int xtc_res_set_alert(xtc_res_t *r, xtc_res_kind_t k, double pct);
145XTC_API int xtc_res_set_alert_fn(xtc_res_t *r,
146 void (*fn)(xtc_res_kind_t, int64_t, int64_t, void *),
147 void *user);
148
149/*
150 * Metering attach points (since 1.50). Both are opt-in; until called,
151 * nothing is charged. Pass r == NULL to detach.
152 *
153 * xtc_res_attach_mctx: charge every chunk of memory context `m`
154 * (payload plus its fixed per-chunk header) to r's XTC_RES_MEM_BYTES.
155 * The context's CURRENT footprint is charged at attach time; if that
156 * alone exceeds the cap the call fails with XTC_E_RESOURCE and
157 * changes nothing. Afterwards an allocation that would pass the cap
158 * is REFUSED: xtc_mctx_alloc/_calloc/_strdup return NULL without
159 * touching the heap. xtc_mctx_free / _reset / _destroy give the
160 * bytes back; detaching (or re-attaching elsewhere) moves the current
161 * footprint. Children created while attached inherit r (a child
162 * created earlier is not affected). Call at setup time: attach must
163 * not race an alloc/free on the same context. XTC_E_INVAL if m is
164 * NULL.
165 *
166 * xtc_res_attach_net: PROCESS-WIDE. Every fd a creating xtc_net call
167 * hands out is charged one XTC_RES_FDS unit to r BEFORE the socket is
168 * made; past the cap the call returns XTC_E_RESOURCE and creates
169 * nothing (a refused dial sends no SYN). xtc_net_close releases the
170 * unit to whichever accountant the fd was charged to, so a later
171 * detach does not unbalance fds already out. Close metered fds with
172 * xtc_net_close; a raw close(2) leaks the unit until the kernel
173 * reuses that fd number for another metered socket. Always XTC_OK.
174 */
175struct xtc_mctx;
176XTC_API int xtc_res_attach_mctx(xtc_res_t *r, struct xtc_mctx *m);
177XTC_API int xtc_res_attach_net(xtc_res_t *r);
178
179#endif /* XTC_RES_H */