Skip to main content

decommit_lazy

Function decommit_lazy 

Source
pub unsafe fn decommit_lazy(base: *mut u8, start: usize, end: usize)
Expand description

Lazy decommit variant: hint the OS it MAY reclaim [base+start, base+end) under memory pressure, cheaper than decommit (Linux MADV_FREE, macOS/iOS MADV_FREE_REUSABLE, FreeBSD/DragonFly MADV_FREE, NetBSD/OpenBSD MADV_FREE, other Unix (including tvOS/watchOS) falls back to MADV_DONTNEED; Windows falls back to the eager decommit path, which has no lazy equivalent).

Unlike decommit, on Linux the pages are NOT necessarily zeroed on next access if the kernel has not yet reclaimed them (a write before reclamation keeps the old contents and cancels the free) — so this is appropriate only for memory whose contents the caller no longer needs but has not yet overwritten. Cheaper reclaim; the kernel takes pages only under pressure. This benign-re-fault story is Linux-only: on Windows this call is the eager decommit path (see the summary above), where a write into the range before recommit is a hard STATUS_ACCESS_VIOLATION crash, not a re-fault — see decommit’s platform-divergence paragraph above for the incident this already caused.

On macOS/iOS specifically, the cost ordering above is INVERTED, on the RSS axis only — see decommit’s Darwin caveat: eager decommit’s MADV_DONTNEED is a no-op there (drops nothing), while this lazy variant’s MADV_FREE_REUSABLE DOES drop the physical footprint immediately (not just “under pressure”). Neither call zero-fills on next access on macOS/iOS — that half of the non-guarantee is unchanged from the eager path. On tvOS/watchOS this function falls back to the same MADV_DONTNEED as decommit (see the “other Unix” case in the summary above — the arm that excludes macOS/iOS specifically, not “other Unix” in a general sense), so there it IS a true no-op like the eager path, on both axes. This tvOS/watchOS fallback is this crate’s current madv_free_advice cfg coverage (REASONED-FROM-SPEC, not verified on tvOS/watchOS hardware or a tvOS/watchOS build target – neither is available to this crate’s CI): MADV_FREE_REUSABLE’s numeric value is defined by XNU, the kernel all four Darwin targets share, so it MAY work identically there too; but tvOS/watchOS’s userspace sandbox restrictions are unverified for this specific advice value, so this is a plausible widening candidate, not an established fact (see madv_free_advice’s doc and https://github.com/PHPCraftdream/sefer-alloc/blob/main/docs/CORRECTNESS_OPEN_ITEMS.md item 48’s S9 note, which must agree with this wording – keep both in sync if either changes).

No fallible form: this entry point is intentionally infallible, for the same safety rationale as decommit. The () return carries no write-permitting sentinel, so silently skipping on a contract violation is safe. A try_decommit_lazy could be added as a future additive API decision.

start/end requirements and the # Safety contract are the same as decommit’s, with ONE deliberate behavioral difference (settled by task #1072): a VIOLATED range here is a silent no-op on EVERY build profile. The eager decommit trips its debug_assert! tripwire in debug builds, and try_decommit reports the violation as Err on every profile; this lazy variant has neither. See decommit’s “Contract violations, by build profile” paragraph for the full split.

§Safety

Same contract as decommit, with the bound restated here in full rather than only referenced (task #1235, applying task #1213/L2’s rule — the one task #1229/F6 already applied to try_recommit): this function does not forward through decommit; its non-mock arm calls the same backend, decommit_pages_impl(base, start, end, DecommitKind::Lazy), directly, and on Windows there is no lazy/eager split — the identical base.add(start) arithmetic before VirtualFree(MEM_DECOMMIT) runs from THIS entry point — so a caller auditing only this section must see the bound, not chase a reference to another function’s # Safety:

  • base must be the as_ptr of a live reservation the caller owns.
  • end <= reservation.len() (the reservation’s usable span, in bytes) — MANDATORY, for the reasons decommit’s own # Safety bullet states in full (both real backends compute base.add(start) and nothing from end; with start <= end the bound is what keeps that offset in-bounds and the OS call’s span [base+start, base+end) inside the reservation). Violating it is undefined behavior, distinct from — and a strictly worse violation than — the page_size()-multiple / start <= end range contract, which here (the deliberate task #1072 difference above) is a silent no-op on EVERY build profile, never UB.
  • [base+start, base+end) must contain no data the caller still needs — its contents are discarded (on the lazy MADV_FREE-family paths the discard is deferred and a write before reclamation cancels it; see the summary above for the per-platform split).