aligned_vmem/decommit_outcome.rs
1use crate::error::VmemError;
2
3/// The observed result of one [`try_decommit`](crate::try_decommit) /
4/// [`Reservation::try_decommit`](crate::Reservation::try_decommit) call —
5/// **task #1180 (PUB-R2 phase 2)**, replacing the pre-#1180
6/// `Result<(), VmemError>` return, which reported only whether the
7/// CALLER'S ARGUMENTS were valid, never what the OS actually did (or was
8/// even asked to do).
9///
10/// The outer `Result<DecommitOutcome, VmemError>` still reports a caller
11/// contract violation exactly as before (`Err(VmemError::invalid_argument())`
12/// for a malformed range, `Err(VmemError::os_refusal_unknown_code())` if the
13/// one-time OS page-size query failed) — see
14/// [`try_decommit`](crate::try_decommit)'s own `# Errors` section. What is
15/// new is the `Ok` payload: three variants that distinguish "no backend
16/// call was made" from "the backend call was made and refused" from "the
17/// SELECTED BACKEND accepted the request", where the pre-#1180 signature
18/// collapsed all three into the same `Ok(())`. That acceptance does NOT
19/// by itself imply that a real OS syscall ran — under the
20/// `aligned_vmem_mock` cfg or miri no syscall runs at all, and `Advised`
21/// is the simulated backend's own unconditional answer (see
22/// [`DecommitOutcome::Advised`]'s own doc for the per-backend meaning).
23///
24/// **None of the three variants is a claim about physical memory having
25/// actually been reclaimed.** Decommit is best-effort by nature (see
26/// [`Reservation::decommit_reclaims_and_zeroes`](crate::Reservation::decommit_reclaims_and_zeroes)
27/// for which platforms guarantee reclaim+zero-fill at all) — this type
28/// answers "what did this call do", not "what did it accomplish".
29#[non_exhaustive]
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum DecommitOutcome {
32 /// No backend call was made — a Rust-level skip, decided before any
33 /// syscall. Two sources, both a well-formed range:
34 ///
35 /// - **An empty range** (`start == end`), on either
36 /// [`try_decommit`](crate::try_decommit) or
37 /// [`Reservation::try_decommit`](crate::Reservation::try_decommit) — a
38 /// deliberate no-op, checked before any huge-page eligibility question
39 /// even applies.
40 /// - **A well-formed, in-span, non-empty range on a huge-page
41 /// reservation** ([`Reservation::is_huge`](crate::Reservation::is_huge)
42 /// == `true`) that does not take the Linux/Android kernel >= 5.18
43 /// huge-aligned real-backend path — see
44 /// [`Reservation::decommit`](crate::Reservation::decommit)'s "Huge-page
45 /// granularity" doc for the exact eligibility split (Windows: always;
46 /// Linux/Android: only a range that is NOT huge-page-size-aligned at
47 /// both endpoints, or when the `huge-pages` feature is off). Only
48 /// [`Reservation::try_decommit`](crate::Reservation::try_decommit) has
49 /// an [`is_huge()`](crate::Reservation::is_huge) to consult, so this
50 /// second source is exclusive to it — the free
51 /// [`try_decommit`](crate::try_decommit) function has no such
52 /// eligibility check and, for a non-empty range, always forwards to the
53 /// backend.
54 Skipped,
55 /// **The SELECTED BACKEND accepted the request.** What that means
56 /// depends on which backend is actually compiled in — this variant does
57 /// NOT by itself imply that a real OS syscall ran:
58 ///
59 /// - **Native backend** (no `aligned_vmem_mock` cfg, not miri): a real
60 /// syscall was made and the kernel/OS accepted it — Linux
61 /// `madvise(2)` returned `0`, or Windows `VirtualFree(MEM_DECOMMIT)`
62 /// returned nonzero (success).
63 /// - **`aligned_vmem_mock` cfg** (`RUSTFLAGS="--cfg aligned_vmem_mock"`):
64 /// no syscall runs at all — the mock backend records the call into its
65 /// call log and unconditionally reports `Advised`, without touching
66 /// the OS (see the `crate::mock` module doc). This is a deliberate
67 /// simulation, not an OS acceptance.
68 /// - **miri**: the backend is a no-op that always "succeeds" — miri
69 /// models no RSS, so there is no real syscall to refuse.
70 ///
71 /// **Never a claim that physical pages were actually returned to the
72 /// OS**, even on the native backend — let alone that a subsequent access
73 /// re-faults zeroed memory. That gap between "the kernel accepted the
74 /// advice" and "the kernel acted on the advice as this crate's docs
75 /// describe" is exactly what
76 /// [`Reservation::decommit_reclaims_and_zeroes`](crate::Reservation::decommit_reclaims_and_zeroes)
77 /// answers — it already reports `false` under `aligned_vmem_mock` and
78 /// under miri (in addition to Darwin/BSD), precisely because "the
79 /// selected backend accepted the request" and "the OS actually
80 /// reclaimed physical memory" are two different questions, and that
81 /// query is the one that distinguishes them; `Advised` is not a second,
82 /// competing channel for the same distinction and must not be read as
83 /// one. On Darwin/the four BSDs specifically (the native backend, not
84 /// mock/miri), `MADV_DONTNEED` is well known to return `0` while the
85 /// pages stay resident (advisory semantics) — `Advised` there is
86 /// expected and unremarkable, not a stronger signal than the platform
87 /// actually gives.
88 ///
89 /// **No separate `Simulated` variant, by design (task #1212).** This
90 /// type is already `#[non_exhaustive]`, so adding a variant later would
91 /// NOT be a semver break — every external `match` on `DecommitOutcome`
92 /// already requires a wildcard arm. A `Simulated` variant was
93 /// considered and deferred (not rejected outright) because the
94 /// mock/miri-vs-real distinction it would carry is already expressed by
95 /// the capability-query family:
96 /// [`Reservation::decommit_reclaims_and_zeroes`](crate::Reservation::decommit_reclaims_and_zeroes)
97 /// and, for the commit side,
98 /// [`lazy_commit_is_honored`](crate::lazy_commit_is_honored) both
99 /// already answer `false` under `aligned_vmem_mock`/miri specifically
100 /// BECAUSE those cfgs substitute simulation for the real OS call — see
101 /// each query's own doc for its exclusion list. A third `Simulated`
102 /// enum variant would duplicate a distinction the crate already exposes
103 /// as a queryable bool; revisit if a caller need emerges that the
104 /// existing query family cannot serve (e.g. wanting to branch on
105 /// simulated-vs-real from a single `DecommitOutcome` value with no
106 /// second call).
107 Advised,
108 /// The backend call was made and the kernel/OS **refused** it — Linux/Android
109 /// `madvise(2)` returned `-1` (e.g. `EINVAL` on a pre-5.18 kernel
110 /// receiving a HugeTLB range, or any other kernel-side rejection), or
111 /// Windows `VirtualFree(MEM_DECOMMIT)` returned zero (failure, e.g.
112 /// `GetLastError()` on a large-page region). Carries
113 /// [`VmemError::last_os_error`] captured immediately after the failing
114 /// call, same capture-timing contract as every other OS-refusal error in
115 /// this crate.
116 ///
117 /// **One optional fault-injection second source of this payload** (task
118 /// #1219): with the `fault-injection` feature enabled AND
119 /// [`fault_injection::arm_fail_next_decommit`](crate::fault_injection::arm_fail_next_decommit)
120 /// armed, the syscall is replaced by a simulated failure and the payload
121 /// is [`VmemError::os_refusal_unknown_code`] instead — no syscall ran, so
122 /// there is no `last_os_error` to capture (the same task-#713 rule the
123 /// commit-side seam follows). `fault-injection` is a public, process-global,
124 /// opt-in Cargo feature (see its own `Cargo.toml` doc comment) — "test-only"
125 /// understates it, since any downstream consumer that enables it can arm
126 /// this path in a production build too. A build without that feature
127 /// enabled can only reach the real-backend path described above.
128 Refused(VmemError),
129}
130
131impl DecommitOutcome {
132 /// `true` for [`DecommitOutcome::Skipped`].
133 #[must_use]
134 #[inline]
135 pub const fn is_skipped(&self) -> bool {
136 matches!(self, DecommitOutcome::Skipped)
137 }
138
139 /// `true` for [`DecommitOutcome::Advised`].
140 #[must_use]
141 #[inline]
142 pub const fn is_advised(&self) -> bool {
143 matches!(self, DecommitOutcome::Advised)
144 }
145
146 /// `true` for [`DecommitOutcome::Refused`].
147 #[must_use]
148 #[inline]
149 pub const fn is_refused(&self) -> bool {
150 matches!(self, DecommitOutcome::Refused(_))
151 }
152}