Expand description
Serialized, panic-safe access to process-global environment variables.
Test-only in purpose, compiled always: mur-agent-runtime’s tests need it
and a #[cfg(test)] item in this crate is invisible to them. It is ~60
lines of dead code in a release binary.
§Why one lock for every variable
setenv(3) may reallocate the environ array, so a concurrent getenv
anywhere in the process — including inside libc or a dependency, for a
variable this test has never heard of — can read freed memory. That is why
Rust 2024 made std::env::set_var unsafe. The hazard is the array, not
the name, so a per-variable lock (which this replaces) gives false comfort:
it orders writers of MUR_HOME against each other and does nothing about
the reader of PATH two threads over.
§What wrapping the MutexGuard costs
clippy::await_holding_lock fires on a bare std::sync::MutexGuard held
across an .await; it does not see one inside a struct, so it is silent
here. The hazard it warns about — a task blocking the thread its holder
needs to resume on — does not arise for #[tokio::test], which gives each
test its own current-thread runtime on its own thread. Holding this guard
across an await in production code would be a different matter, and the
lint would not tell you.
§Why a guard rather than a save/restore pair
The pattern this replaces saved the prior value, set the variable, ran the
test, then restored — with the restore after the assertions. A failing
assertion panics past it, so the variable outlives the TempDir it points
at, and a std::sync::Mutex held across that panic is poisoned for every
test after it. One failed assertion became a file of failures that named
the lock instead of the bug. Restoring in Drop is what makes the restore
actually run; tolerating poison is what keeps the cascade from starting.
Structs§
- EnvGuard
- Holds the process’s environment lock and restores every variable it touched when dropped — including back to absent.