libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_sim.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_sim.h
6 * Deterministic Simulation Testing (DST) seams.
7 *
8 * Phase 0 is the seeded PRNG tree: a single root seed splits into
9 * per-stream sub-streams so a draw at one decision site (steal
10 * victim, placement, lock victim, fault toggle) does not perturb
11 * another site's sequence -- the FoundationDB discipline that keeps
12 * a replay stable when a new draw site is added.
13 *
14 * When sim is INACTIVE (the production default) __xtc_sim_active()
15 * is 0 and the runtime's existing ad-hoc randomness is used
16 * unchanged; the call sites consult __xtc_sim_active() and only draw
17 * from the seeded tree under sim. This module compiles into every
18 * build but is dormant unless a sim run activates it.
19 */
20
21#ifndef XTC_SIM_H
22#define XTC_SIM_H
23
24#include "xtc_export.h"
25
26#include <stdint.h>
27
28/* Well-known PRNG streams. Each scheduler-relevant random draw site
29 * owns a stream so adding a draw in one site cannot shift another. */
30enum xtc_sim_stream {
31 XTC_SIM_RNG_SCHED = 0, /* which runnable loop advances next */
32 XTC_SIM_RNG_STEAL = 1, /* work-stealing victim selection */
33 XTC_SIM_RNG_PLACE = 2, /* round-robin proc/task placement */
34 XTC_SIM_RNG_LOCKVIC = 3, /* lock-manager deadlock victim */
35 XTC_SIM_RNG_IO = 4, /* simulated I/O latency / completion order */
36 XTC_SIM_RNG_FAULT = 5, /* fault-injection toggles */
37 XTC_SIM_RNG_APP = 6, /* application/test draws */
38 XTC_SIM_RNG_BUGGIFY = 7, /* buggify per-call activation coins */
39 XTC_SIM_RNG_NSTREAMS
40};
41
42/*
43 * 1 while a deterministic simulation run is active (set by
44 * xtc_sim_activate, cleared by xtc_sim_deactivate). Hot-path call
45 * sites branch on this to decide between seeded and ad-hoc randomness.
46 *
47 * PUBLIC: int __xtc_sim_active __P((void));
48 * PUBLIC: void __xtc_sim_nondeterminism __P((const char *));
49 * PUBLIC: void xtc_sim_strict __P((int));
50 * PUBLIC: int xtc_sim_nondeterminism_count __P((void));
51 * PUBLIC: void xtc_sim_activate __P((uint64_t));
52 * PUBLIC: void xtc_sim_deactivate __P((void));
53 * PUBLIC: uint64_t __xtc_sim_rng __P((int));
54 * PUBLIC: uint64_t __xtc_sim_rng_range __P((int, uint64_t));
55 * PUBLIC: void xtc_sim_clock_enable __P((int64_t));
56 * PUBLIC: void xtc_sim_clock_disable __P((void));
57 * PUBLIC: void xtc_sim_clock_advance __P((int64_t));
58 * PUBLIC: void xtc_sim_clock_set __P((int64_t));
59 * PUBLIC: int __xtc_sim_vclock __P((int64_t *));
60 */
61XTC_API int __xtc_sim_active(void);
62
63/* Determinism guard. A sim-reachable primitive that would break seed
64 * replay (real clock, unseeded RNG, env read, raw thread id) calls
65 * __xtc_sim_nondeterminism(what); during a sim run it records the
66 * violation and, in strict mode (default), aborts naming the source.
67 * xtc_sim_strict toggles abort-vs-count; xtc_sim_nondeterminism_count
68 * lets a harness assert zero to PROVE a run was fully deterministic. */
69XTC_API void __xtc_sim_nondeterminism(const char *what);
70XTC_API void xtc_sim_strict(int on);
71XTC_API int xtc_sim_nondeterminism_count(void);
72
73/* Clock skew (FoundationDB "the clock is not perfect"). The scheduler
74 * still schedules timers against the true virtual clock, but a fiber
75 * that reads the clock (xtc_proc time, timeout math) observes
76 * true + offset (+/- a seeded jitter), so its notion of elapsed time can
77 * disagree with when its timers actually fire -- stressing code that
78 * assumes wall time equals scheduler time. Seeded and bounded, so it
79 * replays; applied only on the observation seam, so it can never desync
80 * the scheduler. offset_ns is a fixed per-run offset; jitter_ns (>=0)
81 * is the +/- band of a seeded per-read wobble (0 = none). Off by
82 * default; reset on deactivate.
83 *
84 * PUBLIC: void xtc_sim_clock_skew __P((int64_t, int));
85 */
86XTC_API void xtc_sim_clock_skew(int64_t offset_ns, int jitter_ns);
87
88/* Activate sim with a root seed (also resets every stream). Idempotent
89 * re-activation re-seeds. Test-only; never called in production. */
90XTC_API void xtc_sim_activate(uint64_t seed);
91XTC_API void xtc_sim_deactivate(void);
92
93/* Draw the next 64-bit value from stream `s` (0..XTC_SIM_RNG_NSTREAMS-1).
94 * Deterministic given the activation seed. Undefined if sim inactive
95 * (callers must gate on __xtc_sim_active()). */
96XTC_API uint64_t __xtc_sim_rng(int s);
97
98/* Convenience: a uniform value in [0, bound) from stream `s`. bound==0
99 * returns 0. */
100XTC_API uint64_t __xtc_sim_rng_range(int s, uint64_t bound);
101
102/* Deterministic fault toggle: returns 1 with probability
103 * pct_per_1000/1000, drawn from the dedicated FAULT stream (so enabling
104 * faults never shifts the schedule); 0 when sim is inactive. A test
105 * calls it at a fault decision point; the same seed reproduces the
106 * identical fault schedule.
107 *
108 * PUBLIC: int xtc_sim_fault __P((unsigned));
109 */
110XTC_API int xtc_sim_fault(unsigned pct_per_1000);
111
112/*
113 * Critical-section fault points. A fault point marks an interleaving-
114 * sensitive critical section; under sim, when points are enabled,
115 * reaching one draws from the FAULT stream and, on a hit, records the
116 * fire. Enable with a per-1000 fire probability; query coverage with
117 * the fires/seen accessors. Production never
118 * reaches a fire (sim inactive); the call is a single relaxed load.
119 *
120 * PUBLIC: void xtc_sim_fault_points_enable __P((unsigned));
121 * PUBLIC: void xtc_sim_fault_points_disable __P((void));
122 * PUBLIC: int xtc_sim_fault_point __P((const char *));
123 * PUBLIC: uint64_t xtc_sim_fault_point_fires __P((const char *));
124 * PUBLIC: int xtc_sim_fault_points_seen __P((void));
125 */
126XTC_API void xtc_sim_fault_points_enable(unsigned pct_per_1000);
127XTC_API void xtc_sim_fault_points_disable(void);
128XTC_API int xtc_sim_fault_point(const char *name);
129XTC_API uint64_t xtc_sim_fault_point_fires(const char *name);
130XTC_API int xtc_sim_fault_points_seen(void);
131
132/*
133 * Simulated I/O faults (DST). When enabled, the sim I/O backend defers
134 * file-AIO completions by a seeded latency (so completion ORDER across
135 * concurrent ops is part of the replayable schedule) and may inject a
136 * seeded fault (short transfer or EIO). Off by default -- a sim run
137 * that does not enable them gets inline completion. Seeded on the IO
138 * stream, so enabling does not perturb the schedule.
139 *
140 * PUBLIC: void xtc_sim_io_faults_enable __P((int64_t, int64_t, unsigned));
141 * PUBLIC: void xtc_sim_io_faults_disable __P((void));
142 * PUBLIC: int __xtc_sim_io_faults_active __P((void));
143 * PUBLIC: int64_t __xtc_sim_io_latency __P((void));
144 * PUBLIC: int __xtc_sim_io_should_fault __P((void));
145 */
146XTC_API void xtc_sim_io_faults_enable(int64_t lat_min_ns, int64_t lat_max_ns,
147 unsigned fault_pct_per_1000);
148XTC_API void xtc_sim_io_faults_disable(void);
149XTC_API int __xtc_sim_io_faults_active(void);
150XTC_API int64_t __xtc_sim_io_latency(void);
151XTC_API int __xtc_sim_io_should_fault(void);
152
153/*
154 * Simulated TORN / CORRUPT writes and reads (DST) -- the torn-page fault
155 * class FoundationDB models. Distinct from the short-transfer / EIO
156 * faults above (which report a truncated-but-clean result the caller
157 * re-issues): a torn write actually PERSISTS fewer bytes than requested
158 * while still REPORTING full success, and a corrupt read flips a byte in
159 * the returned buffer. Both leave latent bad data a checksum must catch
160 * -- a storage engine's real durability hazard. Off by default; seeded
161 * on the IO stream so enabling does not perturb the schedule. A write
162 * is torn (its persisted prefix chosen) with probability corrupt_pct;
163 * likewise a read is bit-flipped with the same probability.
164 *
165 * PUBLIC: void xtc_sim_io_corrupt_enable __P((unsigned));
166 * PUBLIC: void xtc_sim_io_corrupt_disable __P((void));
167 * PUBLIC: int __xtc_sim_io_corrupt_active __P((void));
168 * PUBLIC: int __xtc_sim_io_torn_prefix __P((int));
169 * PUBLIC: int __xtc_sim_io_flip_byte __P((int));
170 */
171XTC_API void xtc_sim_io_corrupt_enable(unsigned corrupt_pct_per_1000);
172XTC_API void xtc_sim_io_corrupt_disable(void);
173XTC_API int __xtc_sim_io_corrupt_active(void);
174XTC_API int __xtc_sim_io_torn_prefix(int full_len);
175XTC_API int __xtc_sim_io_flip_byte(int len);
176
177/*
178 * Disk-full (ENOSPC) injection: a seeded coin makes a WRITE fail with a
179 * hard out-of-space error mid-workload -- the whole op fails and nothing
180 * persists, distinct from a short transfer or a torn write -- so a
181 * storage engine's graceful-degradation path is exercised. Off by
182 * default; reset via enable(0). A no-op outside a sim run.
183 *
184 * PUBLIC: void xtc_sim_io_enospc_enable __P((unsigned));
185 * PUBLIC: int __xtc_sim_io_enospc __P((void));
186 */
187XTC_API void xtc_sim_io_enospc_enable(unsigned pct_per_1000);
188XTC_API int __xtc_sim_io_enospc(void);
189
190/*
191 * Stale-data reads (FoundationDB's "the disk returns an OLD durable
192 * version"). On a seeded coin a read at an offset that was written,
193 * then overwritten, returns the PRIOR (structurally valid but out of
194 * date) contents -- catching recovery/cache code that skips a
195 * version/LSN check. General (fd+offset level): a bounded ring
196 * snapshots superseded write payloads. Off by default; enable(pct)
197 * arms it and resets the ring for the run. A no-op outside a sim run.
198 *
199 * PUBLIC: void xtc_sim_io_stale_enable __P((unsigned));
200 * PUBLIC: void __xtc_sim_io_stale_record __P((int, uint64_t, const void *, int));
201 * PUBLIC: int __xtc_sim_io_stale_read __P((int, uint64_t, void *, int));
202 */
203XTC_API void xtc_sim_io_stale_enable(unsigned pct_per_1000);
204XTC_API void __xtc_sim_io_stale_record(int fd, uint64_t off, const void *buf, int len);
205XTC_API int __xtc_sim_io_stale_read(int fd, uint64_t off, void *buf, int len);
206
207/*
208 * Write-back cache crash model (FoundationDB's simulated disk): pwrite
209 * lands in a volatile per-fd cache (tracked as the written high-water),
210 * fsync/fdatasync promotes the written extent to durable, and a crash
211 * loses everything past the last fsync. This lets a crash-recovery test
212 * catch a writer that ACKs a commit WITHOUT fsyncing it -- the sim
213 * writes to a real temp file, so without this model the bytes survive a
214 * crash regardless of fsync, hiding an ack-before-durable bug. A test
215 * arms it, and after the crash asks xtc_sim_io_durable_end(fd) for the
216 * true post-crash frontier (last fsync-confirmed byte) to truncate to,
217 * instead of trusting the writer's self-reported durable point. Off
218 * unless armed; bounded fd table.
219 *
220 * PUBLIC: void xtc_sim_io_wb_enable __P((int));
221 * PUBLIC: void __xtc_sim_io_wb_wrote __P((int, uint64_t));
222 * PUBLIC: void __xtc_sim_io_wb_synced __P((int));
223 * PUBLIC: uint64_t xtc_sim_io_durable_end __P((int));
224 */
225XTC_API void xtc_sim_io_wb_enable(int on);
226XTC_API void __xtc_sim_io_wb_wrote(int fd, uint64_t end_off);
227XTC_API void __xtc_sim_io_wb_synced(int fd);
228XTC_API uint64_t xtc_sim_io_durable_end(int fd);
229
230/*
231 * Simulated network partition + message latency (DST). A seeded,
232 * deterministic model of a partitioned / lossy / delayed network at the
233 * cross-LOOP message granularity xtc's sim models: xtc_send between
234 * procs on different loops routes through __mbox_deliver, the single
235 * cross-loop delivery seam. These let a DST test cut message flow
236 * between loops (a partition) and/or defer each cross-loop delivery by a
237 * seeded latency so delivery ORDER is part of the replayable schedule.
238 * All OFF by default (no behaviour change in production or normal sim).
239 *
240 * Loops are identified by pid.loop_id (== exec_id + 1; 0 == standalone).
241 * A blocked edge makes __mbox_deliver DROP the message via the sender's
242 * existing soft-full path (XTC_E_AGAIN), so a partitioned peer never
243 * deadlocks the sim. Scope: the in-process cross-loop path only -- the
244 * real cross-machine raw-socket transport (io_net.c) cannot run under
245 * the single-thread sim and is NOT modelled here.
246 *
247 * PUBLIC: void xtc_sim_partition_set __P((int, int, int));
248 * PUBLIC: void xtc_sim_partition_isolate __P((int));
249 * PUBLIC: void xtc_sim_partition_clear __P((void));
250 * PUBLIC: int __xtc_sim_partition_blocked __P((int, int));
251 * PUBLIC: void xtc_sim_net_latency __P((int64_t, int64_t));
252 * PUBLIC: int64_t __xtc_sim_net_latency __P((void));
253 */
254XTC_API void xtc_sim_partition_set(int src_loop_id, int dst_loop_id, int blocked);
255XTC_API void xtc_sim_partition_isolate(int loop_id);
256XTC_API void xtc_sim_partition_clear(void);
257XTC_API int __xtc_sim_partition_blocked(int src_loop_id, int dst_loop_id);
258XTC_API void xtc_sim_net_latency(int64_t min_ns, int64_t max_ns);
259XTC_API int64_t __xtc_sim_net_latency(void);
260
261/* Plant a critical-section fault point in runtime code. A single
262 * relaxed load in production (sim inactive); under sim with points
263 * enabled it perturbs/records per the FAULT stream. Elided entirely
264 * with XTC_INJECT_DISABLE, matching XTC_INJECTION_POINT. */
265#if defined(XTC_INJECT_DISABLE)
266# define XTC_SIM_FAULT_POINT(name) ((void)0)
267#else
268# define XTC_SIM_FAULT_POINT(name) ((void)xtc_sim_fault_point(name))
269#endif
270
271/*
272 * Buggify (FoundationDB-style). A named point in the REAL runtime code
273 * that, under sim, lets the code take a legal-but-pessimal path. Unlike
274 * xtc_sim_fault (a fresh draw per call), a buggify point is a coin
275 * flipped ONCE per run per site: decided on first reach, cached, and
276 * every later reach of the same name returns the same decision -- so a
277 * buggified site is consistent within a run and the run replays. 0 in
278 * production / when disabled. Enable with a per-1000 activation
279 * probability; query coverage with the active-count.
280 *
281 * PUBLIC: void xtc_sim_buggify_enable __P((unsigned));
282 * PUBLIC: void xtc_sim_buggify_disable __P((void));
283 * PUBLIC: int xtc_sim_buggify __P((const char *));
284 * PUBLIC: int xtc_sim_buggify_active_count __P((void));
285 * PUBLIC: int xtc_sim_buggify_reached_count __P((void));
286 * PUBLIC: int xtc_sim_buggify_site __P((int, char *, size_t, int *));
287 * PUBLIC: int xtc_sim_buggify_fault __P((unsigned));
288 */
289XTC_API void xtc_sim_buggify_enable(unsigned pct_per_1000);
290XTC_API void xtc_sim_buggify_disable(void);
291XTC_API int xtc_sim_buggify(const char *name);
292XTC_API int xtc_sim_buggify_active_count(void);
293XTC_API int xtc_sim_buggify_reached_count(void);
294XTC_API int xtc_sim_buggify_site(int idx, char *buf, size_t buflen,
295 int *out_activated);
296XTC_API int xtc_sim_buggify_fault(unsigned pct_per_1000);
297
298/* Branch on a buggify point in runtime code:
299 * if (XTC_SIM_BUGGIFY("wal.flush.tiny_batch")) { ... pessimal ... }
300 * A single relaxed load in production; elided with XTC_INJECT_DISABLE. */
301#if defined(XTC_INJECT_DISABLE)
302# define XTC_SIM_BUGGIFY(name) (0)
303#else
304# define XTC_SIM_BUGGIFY(name) xtc_sim_buggify(name)
305#endif
306
307/* Virtual (logical) clock. When enabled, __os_clock_mono returns the
308 * virtual time instead of the host monotonic clock, so time is a pure
309 * function of the schedule. Test/scheduler-only. */
310XTC_API void xtc_sim_clock_enable(int64_t start_ns);
311XTC_API void xtc_sim_clock_disable(void);
312XTC_API void xtc_sim_clock_advance(int64_t delta_ns);
313XTC_API void xtc_sim_clock_set(int64_t ns);
314
315/* Query the virtual clock: returns 1 and writes *out_ns when active,
316 * 0 otherwise. The single seam __os_clock_mono consults. */
317XTC_API int __xtc_sim_vclock(int64_t *out_ns);
318
319/*
320 * Run an executor's loops deterministically (DST scheduler). Activates
321 * sim with `seed` + the virtual clock, then drives the N loops as N
322 * cooperatively-scheduled entities on the calling thread under a
323 * seed-determined interleaving, until quiescence, the step budget
324 * (max_steps; <= 0 = unbounded), or xtc_exec_stop. Requires a sim
325 * build (--with-io-backend=sim). Returns XTC_OK on quiescence,
326 * XTC_E_AGAIN if the budget was hit with work remaining, or a negative
327 * code on a loop-step error. Declared opaquely (xtc_exec is defined in
328 * xtc_exec.h).
329 *
330 * PUBLIC: int xtc_sim_exec_run __P((struct xtc_exec *, uint64_t, long));
331 */
332struct xtc_exec;
333XTC_API int xtc_sim_exec_run(struct xtc_exec *e, uint64_t seed, long max_steps);
334
335/* Structural invariant checker (run after each sim step): returns
336 * XTC_OK if all per-loop invariants hold, XTC_E_INTERNAL on the first
337 * violation. A 64-bit digest of observable per-loop state for replay
338 * equality (same seed+config -> same hash). Both take an xtc_exec.
339 *
340 * PUBLIC: int xtc_sim_check __P((struct xtc_exec *));
341 * PUBLIC: uint64_t xtc_sim_state_hash __P((struct xtc_exec *));
342 */
343XTC_API int xtc_sim_check(struct xtc_exec *e);
344XTC_API uint64_t xtc_sim_state_hash(struct xtc_exec *e);
345
346/*
347 * Adversarial scheduler bias (FoundationDB "hunt the worst interleaving"
348 * rather than a benign uniform-random order). When enabled, the sim
349 * scheduler, on a seeded XTC_SIM_RNG_SCHED coin, takes the PESSIMAL
350 * pick: it keeps running one pinned loop as long as that loop stays
351 * runnable -- monopolizing the executor and starving every peer (the
352 * classic worst order: a fiber holding a resource a peer is blocked on
353 * never yields). When the pin parks, a new victim is pinned (the
354 * least-recently-run runnable loop, so starvation rotates over a long
355 * run). pct_per_1000 is how often the pessimal pick is taken (0
356 * disables; e.g. 500 = half the picks monopolize, half uniform, so
357 * both orders are still explored across a seed sweep). OFF by default
358 * -- the default uniform pick keeps existing tests' schedules stable;
359 * opt in from an adversarial test / swarm. A no-op outside a sim run.
360 *
361 * PUBLIC: void xtc_sim_sched_pessimal __P((unsigned));
362 * PUBLIC: int __xtc_sim_sched_pessimal_pct __P((void));
363 */
364XTC_API void xtc_sim_sched_pessimal(unsigned pct_per_1000);
365XTC_API int __xtc_sim_sched_pessimal_pct(void);
366
367/*
368 * Completion / message SWIZZLE (reorder), independent of latency. The
369 * sim I/O event queue is normally due-time ordered; a deferred AIO
370 * completion or a cross-loop message is delivered in due order, so
371 * reordering only happens as a side effect of latency jitter. When
372 * swizzle is enabled, on a seeded XTC_SIM_RNG_IO coin an event is
373 * inserted one slot LATER than its due order would place it -- a
374 * legal reordering (the waiter simply wakes after a sibling completion
375 * it would otherwise have preceded), which explicitly explores
376 * completion/message-order interleavings the way FoundationDB swizzles
377 * connection and disk completions. pct_per_1000 is the per-insert
378 * reorder probability (0 disables). OFF by default. A no-op outside
379 * a sim run.
380 *
381 * PUBLIC: void xtc_sim_swizzle_enable __P((unsigned));
382 * PUBLIC: void xtc_sim_swizzle_disable __P((void));
383 * PUBLIC: int __xtc_sim_swizzle_pct __P((void));
384 */
385XTC_API void xtc_sim_swizzle_enable(unsigned pct_per_1000);
386XTC_API void xtc_sim_swizzle_disable(void);
387XTC_API int __xtc_sim_swizzle_pct(void);
388
389/*
390 * Semantic consistency check (FoundationDB's end-of-test consistency
391 * workload). Install a callback that the sim runs ONCE at quiescence,
392 * after all the seeded faults/chaos, to assert a GLOBAL application
393 * invariant a per-step structural state hash cannot see -- e.g. "the
394 * B-tree is still well formed and holds exactly the acked-commit set".
395 * The callback returns XTC_OK if the invariant holds, nonzero otherwise;
396 * the sim propagates a nonzero result as the run's failure. NULL (the
397 * default) means no check. Set it before each run. A no-op in
398 * production.
399 *
400 * PUBLIC: void xtc_sim_set_consistency_check __P((xtc_sim_consistency_fn, void *));
401 * PUBLIC: int __xtc_sim_run_consistency_check __P((void));
402 */
403typedef int (*xtc_sim_consistency_fn)(void *arg);
404XTC_API void xtc_sim_set_consistency_check(xtc_sim_consistency_fn fn, void *arg);
405XTC_API int __xtc_sim_run_consistency_check(void);
406
407#endif /* XTC_SIM_H */