use dashmap::DashMap;
use parking_lot::Mutex;
use vortex_error::vortex_panic;
static ENV_LOCKS: std::sync::LazyLock<DashMap<&'static str, &'static Mutex<()>>> =
std::sync::LazyLock::new(DashMap::new);
fn get_or_create_lock(key: &'static str) -> &'static Mutex<()> {
*ENV_LOCKS
.entry(key)
.or_insert_with(|| Box::leak(Box::new(Mutex::new(()))))
}
pub struct EnvVarGuard {
key: &'static str,
#[expect(dead_code)]
lock_guard: parking_lot::MutexGuard<'static, ()>,
}
const LOCK_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
impl EnvVarGuard {
fn acquire_lock(key: &'static str) -> parking_lot::MutexGuard<'static, ()> {
let mutex = get_or_create_lock(key);
match mutex.try_lock_for(LOCK_TIMEOUT) {
Some(guard) => guard,
None => vortex_panic!(
"EnvVarGuard: timed out after {LOCK_TIMEOUT:?} waiting for environment variable '{key}'. \
This likely indicates a deadlock - ensure guards for the same key are properly scoped \
taken in lexicographical order and dropped before acquiring a new one."
),
}
}
pub fn set(key: &'static str, value: &str) -> Self {
let lock_guard = Self::acquire_lock(key);
unsafe {
std::env::set_var(key, value);
}
Self { key, lock_guard }
}
pub fn remove(key: &'static str) -> Self {
let lock_guard = Self::acquire_lock(key);
unsafe {
std::env::remove_var(key);
}
Self { key, lock_guard }
}
}
impl Drop for EnvVarGuard {
fn drop(&mut self) {
unsafe {
std::env::remove_var(self.key);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_and_remove() {
let key = "VORTEX_TEST_ENV_VAR_SET";
assert!(std::env::var(key).is_err());
{
let _guard = EnvVarGuard::set(key, "test_value");
assert_eq!(std::env::var(key).unwrap(), "test_value");
}
assert!(std::env::var(key).is_err());
}
#[test]
fn test_remove() {
let key = "VORTEX_TEST_ENV_VAR_REMOVE";
unsafe {
std::env::set_var(key, "initial");
}
{
let _guard = EnvVarGuard::remove(key);
assert!(std::env::var(key).is_err());
}
assert!(std::env::var(key).is_err());
}
#[test]
fn test_second_guard_waits_for_first() {
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
let key = "VORTEX_TEST_ENV_VAR_WAIT";
let second_acquired = Arc::new(AtomicBool::new(false));
let second_acquired_clone = second_acquired.clone();
let _guard1 = EnvVarGuard::set(key, "first");
assert_eq!(std::env::var(key).unwrap(), "first");
let handle = thread::spawn(move || {
let _guard2 = EnvVarGuard::set(key, "second");
second_acquired_clone.store(true, Ordering::SeqCst);
assert_eq!(std::env::var(key).unwrap(), "second");
});
thread::sleep(Duration::from_millis(50));
assert!(!second_acquired.load(Ordering::SeqCst));
drop(_guard1);
handle.join().unwrap();
assert!(second_acquired.load(Ordering::SeqCst));
}
}