Skip to main content

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}