libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_batch.h
1/*-
2 * Copyright (c) 2026, The XTC Project
3 * @stability experimental
4 * Use of this source code is governed by the ISC License.
5 */
6#ifndef XTC_BATCH_H
7#define XTC_BATCH_H
8
9#include <stddef.h>
10#include <stdint.h>
11#include "xtc_exec.h"
12#include "xtc_future.h"
13#include "xtc_accel.h"
14
15#define XTC_BATCH_VERSION 1
16
17typedef struct xtc_batch xtc_batch_t;
18typedef struct xtc_batch_item {
19 uint64_t handle;
20 void *request;
22
23enum xtc_batch_state {
24 XTC_BATCH_UNSET,
25 XTC_BATCH_CONTINUE,
26 XTC_BATCH_DONE,
27 XTC_BATCH_BLOCKED,
28 XTC_BATCH_ERROR
29};
30typedef struct xtc_batch_result {
31 enum xtc_batch_state state;
32 int error;
34
35typedef struct xtc_batch_opts {
36 uint32_t size, version;
37 uint32_t capacity, max_active;
38 uint64_t resource_capacity;
39 xtc_exec_class_t sched_class;
40 int (*step)(const xtc_batch_item_t *, xtc_batch_result_t *, size_t,
41 void *);
42 void (*complete)(uint64_t, void *, int, void *);
43 void *user;
45
46enum xtc_batch_close_mode { XTC_BATCH_DRAIN, XTC_BATCH_CANCEL };
47
48/* Async offload (--enable-sched-policy). A group may run a split step: submit
49 * launches ONE device/pool op over the ready vector, the engine awaits a
50 * completion token, then collect reads outputs. See xtc_batch(3). */
51enum xtc_batch_exec_kind {
52 XTC_BATCH_EXEC_SYNC, /* done inline in submit; no park */
53 XTC_BATCH_EXEC_FENCE, /* pollable fence fd (native only) */
54 XTC_BATCH_EXEC_FUTURE, /* app resolves an xtc_future (sim-clean) */
55 XTC_BATCH_EXEC_BLOCKING /* synchronous closure on the blocking pool */
56};
57/* exec_status passed to collect: a non-negative engine sentinel below, or a
58 * NEGATIVE XTC_E_* when the engine could not await the op (bad/closed fence
59 * fd, no accel support, refused blocking closure, failed/dropped device
60 * promise); every member then fails with that code. Only EXEC_OK means the
61 * op finished: after any other status it may still be running, and no
62 * member is resubmitted (see xtc_batch(3)). */
63#define XTC_BATCH_EXEC_OK 0
64#define XTC_BATCH_EXEC_TIMEOUT 1 /* exec_timeout_ns elapsed (FENCE/FUTURE) */
65#define XTC_BATCH_EXEC_ABORTED 2 /* cancel-closed or driver killed mid-op */
66typedef struct xtc_batch_exec {
67 enum xtc_batch_exec_kind kind;
68 int fence_fd; /* FENCE: borrowed; app closes */
69 xtc_future_t *future; /* FUTURE: app resolves */
70 int (*run)(void *); /* BLOCKING: synchronous closure */
71 void *arg;
73
74typedef struct xtc_batch_async_opts {
75 uint32_t size, version;
76 uint32_t capacity, max_active;
77 uint64_t resource_capacity;
78 /* Cross-loop admission (submit_mt/cancel_mt/resume_mt). 0 disables mt
79 * submission for this group (submit_mt returns XTC_E_NOSYS). When set,
80 * it bounds BOTH the pending hand-in ring and its preallocated record
81 * pool; distinct from `capacity` (admitted live requests). */
82 uint32_t mt_queue_cap;
83 int64_t exec_timeout_ns; /* 0 = none; FENCE/FUTURE wait only */
84 xtc_exec_class_t sched_class;
85 int (*submit)(const xtc_batch_item_t *, size_t, void *,
87 int (*collect)(const xtc_batch_item_t *, xtc_batch_result_t *, size_t,
88 int, void *);
89 void (*complete)(uint64_t, void *, int, void *);
90 /* Optional: fires once per in-flight member when its cancel lands,
91 * while the op is still outstanding. */
92 void (*cancel_hint)(uint64_t, void *);
93 void *user;
95
96typedef struct xtc_batch_async_stats {
97 uint32_t size, version;
98 uint64_t offloads, offload_timeouts, offload_errors;
99 /* mt_queued: hand-ins pushed through the admission ring over the
100 * group's life (monotonic). mt_rejections: submit_mt/cancel_mt/
101 * resume_mt rejected because the ring or record pool was full, or
102 * submit_mt refused because the group was closed. */
103 uint64_t mt_queued, mt_rejections;
105
106typedef struct xtc_batch_stats {
107 uint32_t size, version;
108 uint32_t queued, ready, blocked;
109 uint64_t reserved_units, iterations, completed, cancelled, failed;
110 uint64_t rejections, max_vector, total_members;
112
113#ifdef __cplusplus
114extern "C" {
115#endif
116/* Setup is externally serialized, before the loop's first run/step. */
117XTC_API int
118xtc_batch_create(xtc_loop_t *, const xtc_batch_opts_t *, xtc_batch_t **);
119/* Mutations: pre-run or actual owner loop only; callbacks in flight => DEADLK.
120 * Requests are borrowed through completion. Handles belong to one group. */
121XTC_API int xtc_batch_submit(xtc_batch_t *, void *, uint64_t, uint64_t *);
122XTC_API int xtc_batch_cancel(xtc_batch_t *, uint64_t);
123XTC_API int xtc_batch_resume(xtc_batch_t *, uint64_t);
124XTC_API int xtc_batch_close(xtc_batch_t *, enum xtc_batch_close_mode);
125/* Destroy/stats also permit externally synchronized quiescent post-run calls.
126 * Close -> run until driver ends -> destroy -> loop/exec fini. Stop is not
127 * drain. Initialize output size/version; unknown version => VERSION, wrong size
128 * => INVAL. Feature-disabled builds return NOSYS for every batch operation. */
129XTC_API int xtc_batch_destroy(xtc_batch_t *);
130XTC_API int xtc_batch_get_stats(xtc_batch_t *, xtc_batch_stats_t *);
131/* Async-offload group. Same lifecycle as xtc_batch_create; the step is the
132 * submit/collect pair in opts. submit_mt/placement are NOT part of this.
133 * Disabled builds return NOSYS. */
134XTC_API int
135xtc_batch_create_async(xtc_loop_t *, const xtc_batch_async_opts_t *,
136 xtc_batch_t **);
137XTC_API int xtc_batch_get_async_stats(xtc_batch_t *, xtc_batch_async_stats_t *);
138/* Cross-loop admission (--enable-sched-policy, mt_queue_cap > 0 in
139 * xtc_batch_create_async opts). submit_mt/cancel_mt/resume_mt are callable
140 * from ANY loop or a plain OS thread: they touch only a lock-free admission
141 * ring + record pool + the group's monotonic handle counter, never the
142 * group's slots. The driver (owner loop) drains the ring at each iteration
143 * boundary. submit_mt stamps and returns a handle immediately; admission into
144 * the active set happens at the next boundary (a hand-in that finds the group
145 * full waits, in order, for a slot). Every XTC_OK is followed by exactly one
146 * complete callback. A cancel_mt for a handle the caller was given is always
147 * applied; one for a completed handle is discarded. Returns XTC_E_NOSYS if
148 * mt_queue_cap == 0 (mt not enabled) or the build lacks
149 * --enable-sched-policy; XTC_E_AGAIN if the ring/pool is full or the group is
150 * closed (no handle issued; retry with a yield/sleep, there is no blocking
151 * variant); XTC_E_RANGE on an impossible charge or handle exhaustion.
152 * cancel_mt/resume_mt return XTC_E_INVAL once the driver has finished.
153 * See xtc_batch(3). */
154XTC_API int xtc_batch_submit_mt(xtc_batch_t *, void *, uint64_t, uint64_t *);
155XTC_API int xtc_batch_cancel_mt(xtc_batch_t *, uint64_t);
156XTC_API int xtc_batch_resume_mt(xtc_batch_t *, uint64_t);
157
158/* ----- Placement helpers (--enable-sched-policy) -----------------------
159 * Advisory glue, NOT a scheduler: there is no automatic migration of a
160 * live group. A group stays on its creation loop; these only help a
161 * consumer CHOOSE a loop at create time and shard across a fixed set of
162 * groups it created. Disabled builds return XTC_E_NOSYS. */
163
164/* Map a probed accelerator device to the executor loop index whose
165 * carrier thread is on the same NUMA node as the device (best-effort).
166 * Reads the device's NUMA node from sysfs (static topology, no sim-
167 * nondeterministic source): for a renderD* node from
168 * /sys/class/drm/<name>/device/numa_node, for an accel* node from
169 * /sys/class/accel/<name>/device/numa_node. On success writes a loop
170 * index in [0, xtc_exec_n_loops) to *out_loop_idx and returns XTC_OK.
171 * Falls back to loop 0 (still XTC_OK -- absence is not an error, matching
172 * xtc_accel's convention) when the device numa_node is -1/unknown, the
173 * executor's loops record no known node, or numa_nodes <= 1. A carrier
174 * records its node when it starts, so before the executor has run (when a
175 * group can be created) this currently always returns loop 0. Returns
176 * XTC_E_INVAL on a NULL argument. See xtc_batch(3). */
177XTC_API int xtc_batch_loop_for_device(xtc_exec_t *,
178 const xtc_accel_dev_t *, int *);
179
180/* Keyed-shard router: N groups + a modulo, nothing more. The caller
181 * creates one group per shard loop (xtc_batch_create[_async] per loop,
182 * typically placed with xtc_batch_loop_for_device); the router is pure
183 * glue over those N independent groups and adds NO cross-group runtime
184 * coupling. It does not submit -- the caller does
185 * xtc_batch_submit[_mt](xtc_batch_route(r, key), ...). _create BORROWS
186 * the groups array by copy (does not own the groups); _destroy frees
187 * ONLY the router, never the groups (caller owns their lifetime).
188 * _route returns groups[key % n] (stable key -> shard). _create returns
189 * XTC_E_INVAL on NULL/n<=0, XTC_E_NOMEM on allocation failure,
190 * XTC_E_NOSYS in disabled builds. */
191typedef struct xtc_batch_router xtc_batch_router_t;
192XTC_API int xtc_batch_router_create(xtc_batch_t **groups, int n,
193 xtc_batch_router_t **out);
194XTC_API void xtc_batch_router_destroy(xtc_batch_router_t *);
195XTC_API xtc_batch_t *xtc_batch_route(xtc_batch_router_t *, uint64_t key);
196#ifdef __cplusplus
197}
198#endif
199#endif