libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc.h
1/*-
2 * Copyright (c) 2026, The XTC Project
3 * @stability stable
4 *
5 * Use of this source code is governed by the ISC License,
6 * a copy of which is in the file LICENSE in the top-level directory
7 * of this distribution.
8 *
9 * src/inc/xtc.h
10 * The single public header for the xtc library.
11 * See M0_CLAIMS.md [C4]: including this header alone is sufficient
12 * to use every M0-public API.
13 */
14
15#ifndef XTC_H
16#define XTC_H
17
18#ifdef __cplusplus
19extern "C" {
20#endif
21
22#include <stddef.h>
23#include <stdint.h> /* int64_t for the public clock / atomic helpers */
24
25/*
26 * Compile-time version macros.
27 * These values come from configure-time substitution into xtc_config.h
28 * via the build system; if xtc_config.h is unavailable (rare; e.g. when
29 * a consumer parses just this header), they fall back to known-bad
30 * sentinels so a missing build is detected loudly.
31 */
32#if defined(__has_include)
33# if __has_include("xtc_config.h")
34# include "xtc_config.h"
35# endif
36#endif
37
38#ifndef XTC_VERSION_MAJOR
39# define XTC_VERSION_MAJOR 0
40#endif
41#ifndef XTC_VERSION_MINOR
42# define XTC_VERSION_MINOR 0
43#endif
44#ifndef XTC_VERSION_PATCH
45# define XTC_VERSION_PATCH 0
46#endif
47#ifndef XTC_VERSION_STRING
48# define XTC_VERSION_STRING "0.0.0-unconfigured"
49#endif
50
51/*
52 * Error codes.
53 * 0 == OK; negative values are stable XTC_E_* codes.
54 * Codes are added at the end of the enumeration in minor releases and
55 * never renumbered. See docs/abi-stability.md.
56 */
57typedef enum xtc_err {
58 XTC_OK = 0, /* success */
59 XTC_E_INVAL = -1, /* invalid argument */
60 XTC_E_NOMEM = -2, /* out of memory */
61 XTC_E_NOSYS = -3, /* not implemented on this platform */
62 XTC_E_RANGE = -4, /* numeric out of range */
63 XTC_E_AGAIN = -5, /* try again later */
64 XTC_E_INTERNAL = -6, /* invariant violation; bug */
65 XTC_E_RESOURCE = -7, /* resource cap reached (xtc_res) */
66 XTC_E_DEADLK = -8, /* lock-manager: deadlock victim */
67 XTC_E_VERSION = -9, /* version mismatch (shm) */
68 XTC_E_ABORTED = -10, /* operation cancelled via abort token */
69 XTC_E_NOTFOUND = -11, /* requested item does not exist */
70 XTC_E_IO = -12 /* I/O error (read/write/fsync failed) */
71} xtc_err_t;
72
73/*
74 * Size-first structs (docs/abi-stability.md, "Caller-allocated structs").
75 * Every public option / info / stats struct the caller allocates starts
76 * with `size_t size`, which the caller sets to sizeof its view of the
77 * struct -- the struct's XTC_*_INIT / *_DEFAULT initializer does it. The
78 * library reads an input struct only up to `size` (fields past it take
79 * their defaults) and writes an output struct only up to `size`; size 0 is
80 * XTC_E_INVAL. Fields are only ever APPENDED in a minor release.
81 *
82 * XTC_FIELD_OK(p, field) -- nonzero when `field` lies inside p's declared
83 * size. A consumer reading a struct the LIBRARY sized (one handed to a
84 * callback) checks it before using a field newer than its oldest supported
85 * library: `if (XTC_FIELD_OK(info, mask_depth)) use(info->mask_depth);`.
86 */
87#define XTC_FIELD_OK(p, field) \
88 ((size_t)((const char *)&(p)->field - (const char *)(p)) + \
89 sizeof((p)->field) <= (p)->size)
90
91/*
92 * xtc_version_string --
93 * Return the library version as a NUL-terminated SemVer 2.0 string.
94 * The pointer is to static storage; the caller must not free it.
95 * See M0_CLAIMS.md [C1].
96 */
97const char *xtc_version_string(void);
98
99/*
100 * xtc_version_components --
101 * Decompose the version into three integers.
102 * On success returns XTC_OK and writes *major, *minor, *patch.
103 * On NULL out-pointers returns XTC_E_INVAL.
104 * See M0_CLAIMS.md [C2].
105 */
106int xtc_version_components(int *major, int *minor, int *patch);
107
108/*
109 * xtc_strerror --
110 * Return a stable English description of an xtc error code.
111 * The pointer is to static storage; the caller must not free it.
112 * Returns a pointer to "unknown" for codes outside the known set
113 * rather than NULL, so callers can chain into log lines safely.
114 * See M0_CLAIMS.md [C6].
115 */
116const char *xtc_strerror(int xtc_err);
117
118/*
119 * xtc_free --
120 * Free a heap buffer that a libxtc call handed to the caller to own
121 * -- notably the message buffer from xtc_recv / xtc_recv_match /
122 * xtc_recv_correlate, the frame from xtc_net_recv_frame, the reply
123 * from xtc_osproc_call / xtc_svr_call, and any other buffer whose
124 * documentation says "free with xtc_free". Those buffers are
125 * allocated by libxtc's own allocator, which is not necessarily the
126 * C library malloc/free, so they MUST be released through this
127 * function rather than plain free(). Passing NULL is a no-op.
128 *
129 * This is the public name for the library allocator's deallocation
130 * entry point; it is safe to call from any thread.
131 */
132void xtc_free(void *p);
133
134/*
135 * xtc_malloc / xtc_calloc / xtc_realloc --
136 * Allocate through libxtc's own allocator (the same one xtc_free
137 * releases), so a consumer never needs the internal __os_* surface.
138 * Return the pointer
139 * directly (NULL on failure), matching the C idiom; the result is
140 * released with xtc_free. xtc_calloc zero-fills; xtc_realloc grows
141 * / shrinks an xtc_malloc/xtc_calloc/xtc_realloc block (NULL p acts
142 * like xtc_malloc). A zero size yields a unique freeable pointer,
143 * not NULL. Safe from any thread. These are the public complement
144 * to xtc_free -- library CONSUMERS use only the xtc_* API, never the
145 * internal __os_* wrappers.
146 *
147 * NOTE: there is NO public API for replacing the allocator. The
148 * hook setter (__os_alloc_set_hook) lives in src/inc/os_alloc.h,
149 * which is NOT an installed header, so it is not part of the public
150 * surface; an embedder that needs a custom allocator is building
151 * against the source tree, not the installed API.
152 */
153void *xtc_malloc(size_t size);
154void *xtc_calloc(size_t n, size_t size);
155void *xtc_realloc(void *p, size_t size);
156
157/*
158 * xtc_aligned_alloc / xtc_aligned_free --
159 * Allocate `size` bytes aligned to `align` (a power of two, e.g.
160 * XTC_CACHE_LINE) through libxtc's allocator; release ONLY with
161 * xtc_aligned_free (never xtc_free -- an aligned block may carry
162 * header/padding a plain free would mishandle). Returns NULL on
163 * failure, which covers a bad `align` (not a power of two, or
164 * smaller than sizeof(void *)), OOM, and a `size` so large that
165 * rounding it up to a multiple of `align` would not fit in a size_t
166 * (that last case used to WRAP and hand back a tiny block for a
167 * huge request; it is now rejected). A zero size yields a unique
168 * freeable pointer, not NULL. For a struct with an over-aligned
169 * member.
170 */
171void *xtc_aligned_alloc(size_t align, size_t size);
172void xtc_aligned_free(void *p);
173
174/*
175 * xtc_clock_mono / xtc_clock_real --
176 * Read the monotonic (never goes backward; for intervals/timeouts)
177 * or real (wall-clock; for timestamps) clock in NANOSECONDS. Return
178 * the time directly (0 on the rare query failure). These are the
179 * public clocks a consumer uses instead of raw clock_gettime.
180 *
181 * DETERMINISM (matters if you run under the simulator):
182 * xtc_clock_mono is VIRTUALIZED by the deterministic simulator, so a
183 * seeded run replays its timeline exactly -- it is the clock to use
184 * anywhere a simulated run can reach. xtc_clock_real reads the host
185 * WALL clock, which the simulator does NOT virtualize; calling it on
186 * a sim-reachable path makes the run depend on when it was launched
187 * rather than on the seed, and the determinism guard will flag it
188 * (xtc_sim_exec_run then refuses XTC_OK). Use xtc_clock_real only
189 * for human-facing timestamps outside simulated paths, or derive a
190 * physical component from xtc_clock_mono plus a single epoch offset
191 * sampled at startup.
192 */
193int64_t xtc_clock_mono(void);
194int64_t xtc_clock_real(void);
195
196/*
197 * xtc_sleep_ns --
198 * Sleep the CALLING OS THREAD for at least `ns` nanoseconds. This
199 * is a THREAD sleep (blocking); inside a fiber use xtc_proc_sleep
200 * instead, which parks the fiber and keeps the loop live. Provided
201 * so a consumer never needs raw nanosleep. Returns XTC_OK, or a
202 * negative XTC_E_* on interruption/error.
203 */
204int xtc_sleep_ns(int64_t ns);
205
206/*
207 * xtc_atomic_i64_load / xtc_atomic_i64_add --
208 * Relaxed atomic load, and atomic fetch-add (returns the PRIOR
209 * value), on a shared int64_t. The minimal public atomic surface a
210 * consumer needs for a shared counter / token bucket without reaching
211 * for the internal __os_atomic_* macros or a compiler builtin.
212 */
213int64_t xtc_atomic_i64_load(const int64_t *p);
214int64_t xtc_atomic_i64_add(int64_t *p, int64_t delta);
215
216/*
217 * xtc_env_get / xtc_env_set --
218 * a THREAD-SAFE wrapper over getenv/setenv, which are NOT safe
219 * against each other (a setenv from one thread can reallocate the
220 * environ block a getenv on another thread is reading). Both
221 * serialize on a process-wide lock.
222 *
223 * xtc_env_get returns a pointer to the variable's value, or NULL if
224 * it is unset. The value is copied into a small PER-THREAD buffer,
225 * so the pointer is valid until the NEXT xtc_env_get call ON THE
226 * SAME THREAD (and never invalidated by a setenv from another
227 * thread); copy it if you need to keep it. A value longer than the
228 * thread buffer is truncated (but always NUL-terminated).
229 *
230 * xtc_env_set sets `name` to `value`; when overwrite == 0 an existing
231 * variable is left unchanged. Returns XTC_OK, or a negative XTC_E_*
232 * (XTC_E_INVAL for a NULL/empty/'='-containing name, XTC_E_NOMEM if
233 * the environment cannot grow).
234 */
235const char *xtc_env_get(const char *name);
236int xtc_env_set(const char *name, const char *value, int overwrite);
237
238/*
239 * xtc_rand_u64 / xtc_rand_seed --
240 * a THREAD-SAFE, seedable pseudo-random source. rand(3)/random(3)
241 * share process-global state and are not thread-safe; this is a
242 * PER-THREAD splitmix64 stream, so every thread draws from its own
243 * state with no shared-state race and no contention.
244 *
245 * xtc_rand_u64 returns the next 64-bit value for the calling thread.
246 * xtc_rand_seed makes the calling thread's stream reproducible: the
247 * same seed yields the same sequence. An un-seeded thread auto-seeds
248 * from the clock, so distinct threads get distinct default streams.
249 * Under the deterministic simulator an un-seeded thread draws from
250 * the run's own stream instead, so its values replay from the run's
251 * seed (a seeded thread keeps its own sequence).
252 * This is NOT a cryptographic generator; do not use it for keys or
253 * security tokens.
254 */
255uint64_t xtc_rand_u64(void);
256void xtc_rand_seed(uint64_t seed);
257
258/*
259 * xtc_strlcpy / xtc_strlcat --
260 * bounded string copy/cat with BSD (OpenBSD) semantics, avoiding the
261 * strncpy footgun (it does not NUL-terminate when the source fills
262 * the buffer). These ALWAYS NUL-terminate when dstsize > 0, and
263 * return the total length they TRIED to create -- strlen(src) for
264 * xtc_strlcpy, and the initial strlen(dst) plus strlen(src) for
265 * xtc_strlcat. A return value >= dstsize means the result was
266 * truncated. Pure; no allocation.
267 */
268size_t xtc_strlcpy(char *dst, const char *src, size_t dstsize);
269size_t xtc_strlcat(char *dst, const char *src, size_t dstsize);
270
271/*
272 * xtc_ncpus / xtc_numa_nnodes / xtc_numa_node_of_cpu /
273 * xtc_numa_current_node --
274 * CPU and NUMA topology. Use these when sharding state per core or
275 * per NUMA node (a buffer pool, a partitioned index, a per-core free
276 * list) instead of hard-coding a width or reading sysfs directly.
277 *
278 * xtc_ncpus() is the number of usable CPUs (honoring the cgroup quota
279 * and affinity mask where the platform reports them), always >= 1.
280 * xtc_numa_nnodes() returns 1 on a non-NUMA or unqueryable system, and
281 * xtc_numa_node_of_cpu() then returns 0 -- so a caller can shard by
282 * the returned node with no special case. xtc_numa_current_node() is
283 * the node the CALLING thread is running on right now, which can
284 * change if the thread migrates; treat it as a placement hint.
285 *
286 * All are cheap cached queries, safe from any thread.
287 */
288int xtc_ncpus(void);
289int xtc_numa_nnodes(void);
290int xtc_numa_node_of_cpu(int cpu);
291int xtc_numa_current_node(void);
292
293#ifdef __cplusplus
294}
295#endif
296
297#endif /* XTC_H */