fault-injection only.Expand description
Real-path commit fault injection (feature fault-injection).
Distinct from crate::mock (cfg aligned_vmem_mock, not necessarily set
alongside fault-injection): mock replaces the entire backend for
commit/decommit/recommit (and short-circuits reservations only for the
scripted-failure case) with a thread-local recording stub — a consumer
that needs the REAL OS backend under test (real segment reservations, real
commit accounting, real page-fault behaviour) cannot use it. This module
changes nothing about which backend runs: crate::try_commit_range
calls the real per-OS commit_range_impl — but only when the
aligned_vmem_mock cfg is NOT set (task #1106/L1). Under
aligned_vmem_mock, try_commit_range’s real-path branch — the single
call site of should_fail_commit — is compiled out and replaced by the
mock backend, so this module’s hooks are never consulted: the mock’s own
fault script (crate::mock::fail_next_commit etc. — a separate mechanism,
compiled only under the mock cfg) takes precedence, and arming THESE hooks
is silently inert. CI deliberately builds exactly that combination
(.github/workflows/ci.yml sets the mock cfg alongside the full feature
set including fault-injection), so the combination is real, not
theoretical; the hooks are allow(dead_code) in
it rather than rejected, because the mock rows exercise the same feature
set and a compile_error! on the combination would break them. Outside
the mock cfg, this module splices two armed checks in front of the real
backend call so a test can deterministically force a
specific call to report VmemError::os_refusal_unknown_code() (task #713:
not last_os_error() — no real syscall runs for a simulated fault, so
there is no real OS code to report) instead of touching the OS —
simulating commit-charge exhaustion at an exact point in a real allocation
sequence.
task #1219 adds a decommit-side sibling hook with exactly ONE call site of
its own: arm_fail_next_decommit, consulted from
dispatch_try_decommit (api/decommit.rs) — the single private dispatch
point both fallible decommit entry points (the free try_decommit and
Reservation::try_decommit) funnel through — in front of the real
decommit_pages_impl call. It exists for the same reason and carries the
same mock-caveat as the commit-side hooks (inert under
aligned_vmem_mock, where the call site is compiled out). Only the
fail-next tier exists on the decommit side; no arm_fail_at-style k-th
hook, because no test has needed one — add it by mirroring
arm_fail_at/FAULT_STATE if one ever does.
Two independent, additive hooks (mirrors the two-tier hook that
sefer-alloc carried before this crate absorbed it):
arm_fail_next: the nextnreal commit calls fail.arm_fail_at: the k-th real commit call from now (1-based) fails; one-shot, disarms itself after firing.
arm_fail_next’s “fail next N” is checked first and has priority; when it
is disarmed (0), arm_fail_at’s “fail the k-th” is checked. Both may be
armed simultaneously.
Process-wide atomics (not thread-local): a test typically arms a fault
from one thread and triggers the committing call from another (e.g. an
alloc-xthread reclaim test spawning worker threads while the main test
thread stays armed), so this module does NOT assume the arming and
committing thread are the same (task #718 – an earlier revision of this
doc claimed exactly that “owner-only discipline” assumption and used
Relaxed throughout on that basis; the assumption does not hold for
multi-threaded consumers, so it is not a safe basis for the ordering
choice). Concretely: arm_fail_at now uses a Mutex<FaultState> to
serialize arming and disarming, closing the concurrent re-arm race
(task #1021/R4-8). FAIL_NEXT’s decrement uses [AtomicU32::fetch_update
(a genuine atomic read-modify-write) instead of a separate load then store,
which would otherwise race under concurrent callers and lose or duplicate a
decrement.
Zero cost when the feature is off: this entire module is compiled out
(#[cfg(feature = "fault-injection")] on the mod declaration in
lib.rs), and the call sites that consult it are themselves
#[cfg(feature = "fault-injection")]-gated, so the production path is
byte-identical with the feature disabled.
Functions§
- arm_
fail_ at - Arm the “fail the k-th real commit from now” hook (1-based, one-shot).
The k-th call to the real commit path from now fails; calls already
consumed by
arm_fail_nextare not counted. All other calls (before and after) succeed normally. After firing, the hook disarms itself.k == 0disarms without ever firing. - arm_
fail_ next - Arm the “fail the next N real commits” hook. The next
ncalls to the real commit path (crate::try_commit_range/crate::commit_range) returnErr/falsewithout touching the OS.n == 0disarms. Inert under thealigned_vmem_mockcfg — see the module doc. - arm_
fail_ next_ decommit - Arm the “fail the next N real decommits” hook (task #1219). The next
ncalls through the real decommit dispatch point —dispatch_try_decommit(api/decommit.rs), reached by BOTH fallible decommit entry points, the freecrate::try_decommitandcrate::Reservation::try_decommit— returnOk(DecommitOutcome::Refused(VmemError::os_refusal_unknown_code()))without touching the OS.n == 0disarms.