cargo/core/compiler/
locking.rs

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