Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/cargo/core/compiler/build_runner/compilation_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,11 @@ impl<'a, 'gctx: 'a> CompilationFiles<'a, 'gctx> {
self.layout(unit.kind).build_dir().fingerprint(&dir)
}

pub fn build_unit_lock(&self, unit: &Unit) -> PathBuf {
let dir = self.pkg_dir(unit);
self.layout(unit.kind).build_dir().build_unit_lock(&dir)
}

/// Directory where incremental output for the given unit should go.
pub fn incremental_dir(&self, unit: &Unit) -> &Path {
self.layout(unit.kind).build_dir().incremental()
Expand Down
14 changes: 10 additions & 4 deletions src/cargo/core/compiler/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,13 @@ impl Layout {
// For now we don't do any more finer-grained locking on the artifact
// directory, so just lock the entire thing for the duration of this
// compile.
let artifact_dir_lock =
dest.open_rw_exclusive_create(".cargo-lock", ws.gctx(), "build directory")?;
let artifact_dir_lock = if !is_new_layout {
Some(dest.open_rw_exclusive_create(".cargo-lock", ws.gctx(), "build directory")?)
} else {
None
};

let build_dir_lock = if root != build_root {
let build_dir_lock = if root != build_root && !is_new_layout {
Some(build_dest.open_rw_exclusive_create(
".cargo-lock",
ws.gctx(),
Expand Down Expand Up @@ -222,7 +225,7 @@ pub struct ArtifactDirLayout {
timings: PathBuf,
/// The lockfile for a build (`.cargo-lock`). Will be unlocked when this
/// struct is `drop`ped.
_lock: FileLock,
_lock: Option<FileLock>,
}

impl ArtifactDirLayout {
Expand Down Expand Up @@ -345,6 +348,9 @@ impl BuildDirLayout {
self.build().join(pkg_dir)
}
}
pub fn build_unit_lock(&self, pkg_dir: &str) -> PathBuf {
self.build_unit(pkg_dir).join("primary.lck")
}
/// Fetch the artifact path.
pub fn artifact(&self) -> &Path {
&self.artifact
Expand Down
110 changes: 110 additions & 0 deletions src/cargo/core/compiler/locking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//! This module handles the locking logic during compilation.
//!
//! The locking scheme is based on build unit level locking.
//! Generally a build unit will follow the following flow:
//! 1. Acquire an exclusive lock for the current build unit.
//! 2. Acquire shared locks on all dependency build units.
//! 3. Begin building with rustc
//! 5. Once complete release all locks.
//!
//! [`CompilationLock`] is the primary interface for locking.

use std::{
fs::{File, OpenOptions},
path::{Path, PathBuf},
};

use itertools::Itertools;
use tracing::instrument;

use crate::{
CargoResult,
core::compiler::{BuildRunner, Unit},
};

/// A lock for compiling a build unit.
///
/// Internally this lock is made up of many [`UnitLock`]s for the unit and it's dependencies.
pub struct CompilationLock {
/// The path to the lock file of the unit to compile
unit: UnitLock,
/// The paths to lock files of the unit's dependencies
dependency_units: Vec<UnitLock>,
}

impl CompilationLock {
pub fn new(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Self {
let unit_lock = build_runner.files().build_unit_lock(unit).into();

let dependency_units = build_runner
.unit_deps(unit)
.into_iter()
.map(|unit| build_runner.files().build_unit_lock(&unit.unit).into())
.collect_vec();

Self {
unit: unit_lock,
dependency_units,
}
}

#[instrument(skip(self))]
pub fn lock(&mut self) -> CargoResult<()> {
self.unit.lock_exclusive()?;

for d in self.dependency_units.iter_mut() {
d.lock_shared()?;
}

Ok(())
}
}

/// A lock for a single build unit.
struct UnitLock {
lock: PathBuf,
gaurd: Option<UnitLockGuard>,
}

struct UnitLockGuard {
_handle: File,
}

impl UnitLock {
pub fn lock_exclusive(&mut self) -> CargoResult<()> {
assert!(self.gaurd.is_none());

let lock = file_lock(&self.lock)?;
lock.lock()?;

self.gaurd = Some(UnitLockGuard { _handle: lock });
Ok(())
}

pub fn lock_shared(&mut self) -> CargoResult<()> {
assert!(self.gaurd.is_none());

let lock = file_lock(&self.lock)?;
lock.lock_shared()?;

self.gaurd = Some(UnitLockGuard { _handle: lock });
Ok(())
}
}

impl From<PathBuf> for UnitLock {
fn from(value: PathBuf) -> Self {
Self {
lock: value,
gaurd: None,
}
}
}

fn file_lock<T: AsRef<Path>>(f: T) -> CargoResult<File> {
Ok(OpenOptions::new()
.create(true)
.write(true)
.append(true)
.open(f)?)
}
17 changes: 17 additions & 0 deletions src/cargo/core/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub mod future_incompat;
pub(crate) mod job_queue;
pub(crate) mod layout;
mod links;
mod locking;
mod lto;
mod output_depinfo;
mod output_sbom;
Expand Down Expand Up @@ -95,6 +96,7 @@ use self::output_depinfo::output_depinfo;
use self::output_sbom::build_sbom;
use self::unit_graph::UnitDep;
use crate::core::compiler::future_incompat::FutureIncompatReport;
use crate::core::compiler::locking::CompilationLock;
use crate::core::compiler::timings::SectionTiming;
pub use crate::core::compiler::unit::{Unit, UnitInterner};
use crate::core::manifest::TargetSourcePath;
Expand Down Expand Up @@ -351,7 +353,22 @@ fn rustc(
output_options.show_diagnostics = false;
}
let env_config = Arc::clone(build_runner.bcx.gctx.env_config()?);

let mut lock = if build_runner.bcx.gctx.cli_unstable().build_dir_new_layout {
Some(CompilationLock::new(build_runner, unit))
} else {
None
};

return Ok(Work::new(move |state| {
if let Some(lock) = &mut lock {
lock.lock().expect("failed to take lock");

// TODO: We need to revalidate the fingerprint here as another Cargo instance could
// have already compiled the crate before we recv'd the lock.
// For large crates re-compiling here would be quiet costly.
}

// Artifacts are in a different location than typical units,
// hence we must assure the crate- and target-dependent
// directory is present.
Expand Down
Loading