Skip to main content

cargo/core/compiler/
locking.rs

1//! This module handles the locking logic during compilation.
2
3use crate::util::flock;
4use crate::{
5    CargoResult,
6    core::compiler::{BuildRunner, Unit},
7    util::{FileLock, Filesystem},
8};
9
10use crate::util::data_structures::HashMap;
11use anyhow::bail;
12use std::{
13    fmt::{Display, Formatter},
14    path::PathBuf,
15    sync::RwLock,
16};
17use tracing::instrument;
18
19/// A struct to store the lock handles for build units during compilation.
20pub struct LockManager {
21    locks: RwLock<HashMap<LockKey, FileLock>>,
22}
23
24impl LockManager {
25    pub fn new() -> Self {
26        Self {
27            locks: RwLock::new(HashMap::default()),
28        }
29    }
30
31    /// Takes a shared lock on a given [`Unit`]
32    /// This prevents other Cargo instances from compiling (writing) to
33    /// this build unit.
34    ///
35    /// This function returns a [`LockKey`] which can be used to
36    /// upgrade/unlock the lock.
37    #[instrument(skip_all, fields(key))]
38    pub fn lock_shared(
39        &self,
40        build_runner: &BuildRunner<'_, '_>,
41        unit: &Unit,
42    ) -> CargoResult<LockKey> {
43        let key = LockKey::from_unit(build_runner, unit);
44        tracing::Span::current().record("key", key.0.to_str());
45
46        let mut locks = self.locks.write().unwrap();
47        if let Some(lock) = locks.get_mut(&key) {
48            flock::lock_shared(lock.file())?;
49        } else {
50            let fs = Filesystem::new(key.0.clone());
51            let lock_msg = format!(
52                "{} ({})",
53                unit.pkg.name(),
54                build_runner.files().unit_hash(unit)
55            );
56            let lock = fs.open_ro_shared_create(&key.0, build_runner.bcx.gctx, &lock_msg)?;
57            locks.insert(key.clone(), lock);
58        }
59
60        Ok(key)
61    }
62
63    #[instrument(skip(self))]
64    pub fn lock(&self, key: &LockKey) -> CargoResult<()> {
65        let locks = self.locks.read().unwrap();
66        if let Some(lock) = locks.get(&key) {
67            flock::lock_exclusive(lock.file())?;
68        } else {
69            bail!("lock was not found in lock manager: {key}");
70        }
71
72        Ok(())
73    }
74
75    /// Upgrades an existing exclusive lock into a shared lock.
76    #[instrument(skip(self))]
77    pub fn downgrade_to_shared(&self, key: &LockKey) -> CargoResult<()> {
78        let locks = self.locks.read().unwrap();
79        let Some(lock) = locks.get(key) else {
80            bail!("lock was not found in lock manager: {key}");
81        };
82        flock::lock_shared(lock.file())?;
83        Ok(())
84    }
85
86    #[instrument(skip(self))]
87    pub fn unlock(&self, key: &LockKey) -> CargoResult<()> {
88        let locks = self.locks.read().unwrap();
89        if let Some(lock) = locks.get(key) {
90            flock::unlock(lock.file())?;
91        };
92
93        Ok(())
94    }
95}
96
97#[derive(Debug, Clone, Hash, Eq, PartialEq)]
98pub struct LockKey(PathBuf);
99
100impl LockKey {
101    fn from_unit(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Self {
102        Self(build_runner.files().build_unit_lock(unit))
103    }
104}
105
106impl Display for LockKey {
107    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
108        write!(f, "{}", self.0.display())
109    }
110}