72 lines
2.1 KiB
Rust
Raw Normal View History

//! Cross-process lock used by providers that materialize binaries.
//!
//! Cargo builds and downloads can be requested by multiple local test
//! processes at the same time. The lock keeps those processes from writing the
//! same target/cache path concurrently.
use std::{
fs, io,
path::{Path, PathBuf},
time::{Duration, Instant},
};
use tokio::time::sleep;
use super::types::BinaryProviderError;
const LOCK_RETRY_DELAY: Duration = Duration::from_millis(200);
const LOCK_TIMEOUT: Duration = Duration::from_secs(10 * 60);
/// File-backed lock removed automatically when dropped.
pub(super) struct BinaryProviderLock {
/// Path of the lock file currently owned by this process.
path: PathBuf,
}
impl BinaryProviderLock {
pub(super) async fn acquire(path: &Path) -> Result<Self, BinaryProviderError> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).map_err(|source| BinaryProviderError::Io {
path: parent.to_owned(),
source,
})?;
}
let started = Instant::now();
loop {
match fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
{
Ok(_) => {
return Ok(Self {
path: path.to_owned(),
});
}
Err(source) if source.kind() == io::ErrorKind::AlreadyExists => {
if started.elapsed() >= LOCK_TIMEOUT {
return Err(BinaryProviderError::LockTimeout {
path: path.to_owned(),
});
}
sleep(LOCK_RETRY_DELAY).await;
}
Err(source) => {
return Err(BinaryProviderError::Io {
path: path.to_owned(),
source,
});
}
}
}
}
}
impl Drop for BinaryProviderLock {
fn drop(&mut self) {
drop(fs::remove_file(&self.path));
}
}