| //! This module handles the locking logic during compilation. |
| |
| use crate::util::flock; |
| use crate::{ |
| CargoResult, |
| compiler::{BuildRunner, Unit}, |
| util::{FileLock, Filesystem}, |
| }; |
| |
| use crate::util::data_structures::HashMap; |
| use anyhow::bail; |
| use std::{ |
| fmt::{Display, Formatter}, |
| path::PathBuf, |
| sync::RwLock, |
| }; |
| use tracing::instrument; |
| |
| /// A struct to store the lock handles for build units during compilation. |
| pub struct LockManager { |
| locks: RwLock<HashMap<LockKey, FileLock>>, |
| } |
| |
| impl LockManager { |
| pub fn new() -> Self { |
| Self { |
| locks: RwLock::new(HashMap::default()), |
| } |
| } |
| |
| /// Takes a shared lock on a given [`Unit`] |
| /// This prevents other Cargo instances from compiling (writing) to |
| /// this build unit. |
| /// |
| /// This function returns a [`LockKey`] which can be used to |
| /// upgrade/unlock the lock. |
| #[instrument(skip_all, fields(key))] |
| pub fn lock_shared( |
| &self, |
| build_runner: &BuildRunner<'_, '_>, |
| unit: &Unit, |
| ) -> CargoResult<LockKey> { |
| let key = LockKey::from_unit(build_runner, unit); |
| tracing::Span::current().record("key", key.0.to_str()); |
| |
| let mut locks = self.locks.write().unwrap(); |
| if let Some(lock) = locks.get_mut(&key) { |
| flock::lock_shared(lock.file())?; |
| } else { |
| let fs = Filesystem::new(key.0.clone()); |
| let lock_msg = format!( |
| "{} ({})", |
| unit.pkg.name(), |
| build_runner.files().unit_hash(unit) |
| ); |
| let lock = fs.open_ro_shared_create(&key.0, build_runner.bcx.gctx, &lock_msg)?; |
| locks.insert(key.clone(), lock); |
| } |
| |
| Ok(key) |
| } |
| |
| #[instrument(skip(self))] |
| pub fn lock(&self, key: &LockKey) -> CargoResult<()> { |
| let locks = self.locks.read().unwrap(); |
| if let Some(lock) = locks.get(&key) { |
| flock::lock_exclusive(lock.file())?; |
| } else { |
| bail!("lock was not found in lock manager: {key}"); |
| } |
| |
| Ok(()) |
| } |
| |
| /// Upgrades an existing exclusive lock into a shared lock. |
| #[instrument(skip(self))] |
| pub fn downgrade_to_shared(&self, key: &LockKey) -> CargoResult<()> { |
| let locks = self.locks.read().unwrap(); |
| let Some(lock) = locks.get(key) else { |
| bail!("lock was not found in lock manager: {key}"); |
| }; |
| flock::lock_shared(lock.file())?; |
| Ok(()) |
| } |
| |
| #[instrument(skip(self))] |
| pub fn unlock(&self, key: &LockKey) -> CargoResult<()> { |
| let locks = self.locks.read().unwrap(); |
| if let Some(lock) = locks.get(key) { |
| flock::unlock(lock.file())?; |
| }; |
| |
| Ok(()) |
| } |
| } |
| |
| #[derive(Debug, Clone, Hash, Eq, PartialEq)] |
| pub struct LockKey(PathBuf); |
| |
| impl LockKey { |
| fn from_unit(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Self { |
| Self(build_runner.files().build_unit_lock(unit)) |
| } |
| } |
| |
| impl Display for LockKey { |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| write!(f, "{}", self.0.display()) |
| } |
| } |