xtc_mctx(3)

---

xtc_mctx(3)

hierarchical memory contexts with cleanup callbacks

XTC_MCTX(3) Library Functions Manual XTC_MCTX(3)

xtc_mctx_create, xtc_mctx_destroy, xtc_mctx_alloc, xtc_mctx_calloc, xtc_mctx_strdup, xtc_mctx_free, xtc_mctx_register_cleanup, xtc_mctx_name, xtc_mctx_total_bytes, xtc_mctx_total_chunks, xtc_mctx_reset, xtc_arena_group_create, xtc_arena_group_destroy, xtc_arena_group_mctx, xtc_arena_group_add, xtc_arena_group_discard, xtc_arena_group_size — hierarchical memory contexts with cleanup callbacks

#include <xtc.h>
#include <xtc_mctx.h>

int
xtc_mctx_create(xtc_mctx_t *parent, const char *name, unsigned flags, xtc_mctx_t **out);

void
xtc_mctx_destroy(xtc_mctx_t *ctx);

void *
xtc_mctx_alloc(xtc_mctx_t *ctx, size_t size);

void *
xtc_mctx_calloc(xtc_mctx_t *m, size_t n, size_t size);

void *
xtc_mctx_strdup(xtc_mctx_t *m, const char *s);

void
xtc_mctx_free(xtc_mctx_t *m, void *p);

int
xtc_mctx_register_cleanup(xtc_mctx_t *m, xtc_mctx_cleanup_fn fn, void *user);

const char *
xtc_mctx_name(const xtc_mctx_t *m);

size_t
xtc_mctx_total_bytes(const xtc_mctx_t *m);

size_t
xtc_mctx_total_chunks(const xtc_mctx_t *m);

void
xtc_mctx_reset(xtc_mctx_t *ctx);

int
xtc_arena_group_create(const char *name, xtc_arena_group_t **out);

void
xtc_arena_group_destroy(xtc_arena_group_t *g);

xtc_mctx_t *
xtc_arena_group_mctx(xtc_arena_group_t *g);

int
xtc_arena_group_add(xtc_arena_group_t *g);

int
xtc_arena_group_discard(xtc_arena_group_t *g, int reason, int64_t timeout_ns, int *out_all_gone);

int
xtc_arena_group_size(xtc_arena_group_t *g);

xtc_mctx is a hierarchical memory context, modeled on PostgreSQL's MemoryContext. Allocations belong to a context; destroying the context bulk-frees everything in it (plus all child contexts recursively). Useful for request-scoped memory: create a context per request, allocate freely, destroy at the end without tracking individual frees.

() takes a flags bitmask of XTC_MCTX_* values: XTC_MCTX_DEFAULT (0) for an unlocked context, or XTC_MCTX_THREAD_SAFE for one that is internally locked. A NULL parent produces a root context; name is copied for diagnostics.

Each context can register cleanup callbacks fired on destroy (before the bulk-free) to release non-memory resources.

No context lock is held while a cleanup callback runs, so a callback may call back into its own context ((), (), or an allocation). What it must NOT do is reset or destroy the very context running it. Callbacks are detached before they run, so each fires exactly once even if the context is reset concurrently; register again if the next reset should call it too.

() returns raw memory within the context; () zeroes an n by size array; and () copies string s into the context. All live until the context is reset or destroyed. () optionally releases a single allocation early. () adds a callback fn run in LIFO order at reset/destroy.

() returns the context's name; xtc_mctx_total_bytes() and xtc_mctx_total_chunks() report the memory and chunk count currently charged to the context.

An arena group binds a set of member fibers to one discardable arena, giving a recovery boundary smaller than the whole process -- the analogue of a crash-only database reinitialising shared memory. () makes an empty group with a fresh thread-safe arena, and () frees it (it does not kill members). () returns the arena; members allocate their SHARED state there, and () registers the calling fiber as a member. The group records each member's pid together with its owner loop (the loop it was spawned on), because a pid alone is executor-relative and can name an unrelated process on another standalone loop. A member's owner loop must not be finalized while the group still lists that member: keep it alive until a () reports all_gone == 1, or until xtc_arena_group_destroy(). xtc_arena_group_discard() is the point of the facility: it ()s every live member, waits until ALL are confirmed gone, and only THEN resets the arena -- so the shared state a killed fiber was mid-mutating is thrown away with nothing alive still touching it. If a member is wedged inside a mask (kill deferred) and still alive at its deadline, the arena is left INTACT and the caller is told all_gone == 0, so it escalates to the whole-process fail-stop rather than discard state a live fiber may still touch. () reports the current member count.

xtc_mctx_t *root, *req;
xtc_mctx_create(NULL, "root", XTC_MCTX_DEFAULT, &root);
xtc_mctx_create(root, "request-42", XTC_MCTX_DEFAULT, &req);

/* Allocate request-scoped objects. */
char *buf = xtc_mctx_alloc(req, 4096);
struct widget *w = xtc_mctx_alloc(req, sizeof *w);

/* Done; bulk-free everything in req. */
xtc_mctx_destroy(req);

xtc_slab(3), xtc_res(3), xtc(7)

Appeared in xtc 0.1.

September 21, 2026 Debian

View the mdoc source