use std::{
env,
ffi::{OsStr, OsString},
sync::{Arc, Barrier, mpsc},
thread,
};
use super::{ENV_LOCK, ScopedEnv, remove_env_var_unlocked, set_env_var_unlocked};
pub(super) struct ReleaseOnDrop {
pub(super) sender: Option<mpsc::Sender<()>>,
}
impl Drop for ReleaseOnDrop {
fn drop(&mut self) {
if let Some(sender) = self.sender.take() {
#[expect(
clippy::let_underscore_must_use,
reason = "Receiver may have dropped after a test failure."
)]
let _ = sender.send(());
}
}
}
pub(super) struct RestoreEnv {
pub(super) key: String,
pub(super) original: Option<OsString>,
}
impl Drop for RestoreEnv {
fn drop(&mut self) {
let _guard = ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
match &self.original {
Some(value) => set_env_var_unlocked(OsStr::new(&self.key), value.as_os_str()),
None => remove_env_var_unlocked(OsStr::new(&self.key)),
}
}
}
pub(super) struct ThreadAChannels {
pub(super) barrier: Arc<Barrier>,
pub(super) ready_tx: mpsc::Sender<()>,
pub(super) release_rx: mpsc::Receiver<()>,
pub(super) done_tx: mpsc::Sender<()>,
}
pub(super) struct ThreadBChannels {
pub(super) start_rx: mpsc::Receiver<()>,
pub(super) attempt_tx: mpsc::Sender<()>,
pub(super) acquired_tx: mpsc::Sender<Option<String>>,
pub(super) done_tx: mpsc::Sender<()>,
}
pub(super) fn spawn_inner_guard_thread(
key: String,
channels: ThreadBChannels,
) -> thread::JoinHandle<()> {
let ThreadBChannels {
start_rx,
attempt_tx,
acquired_tx,
done_tx,
} = channels;
thread::spawn(move || {
start_rx.recv().expect("start signal must be received");
attempt_tx.send(()).expect("attempt signal must be sent");
let guard = ScopedEnv::apply(&[(key.clone(), Some(String::from("two")))]);
let value = env::var(&key).ok();
acquired_tx
.send(value)
.expect("acquired value must be sent");
drop(guard);
done_tx.send(()).expect("completion signal must be sent");
})
}
pub(super) fn spawn_outer_guard_thread(
key: String,
channels: ThreadAChannels,
) -> thread::JoinHandle<()> {
let ThreadAChannels {
barrier,
ready_tx,
release_rx,
done_tx,
} = channels;
thread::spawn(move || {
let guard = ScopedEnv::apply(&[(key, Some(String::from("one")))]);
ready_tx.send(()).expect("ready signal must be sent");
barrier.wait();
release_rx.recv().expect("release signal must be received");
drop(guard);
done_tx.send(()).expect("completion signal must be sent");
})
}