Skip to content

Commit 3424d40

Browse files
committed
more complicated locking
1 parent c886e69 commit 3424d40

File tree

4 files changed

+116
-36
lines changed

4 files changed

+116
-36
lines changed

src/cargo/core/compiler/build_runner/compilation_files.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ impl<'a, 'gctx: 'a> CompilationFiles<'a, 'gctx> {
277277
self.layout(unit.kind).build_dir().fingerprint(&dir)
278278
}
279279

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

src/cargo/core/compiler/layout.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,11 @@ impl BuildDirLayout {
348348
self.build().join(pkg_dir)
349349
}
350350
}
351-
pub fn build_unit_lock(&self, pkg_dir: &str) -> PathBuf {
352-
self.build_unit(pkg_dir).join("lock")
351+
pub fn build_unit_lock(&self, pkg_dir: &str) -> (PathBuf, PathBuf) {
352+
(
353+
self.build_unit(pkg_dir).join("primary-lock"),
354+
self.build_unit(pkg_dir).join("secondary-lock"),
355+
)
353356
}
354357
/// Fetch the artifact path.
355358
pub fn artifact(&self) -> &Path {

src/cargo/core/compiler/locking.rs

Lines changed: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{
22
fs::{File, OpenOptions},
3-
path::PathBuf,
3+
path::{Path, PathBuf},
44
};
55

66
use itertools::Itertools;
@@ -9,59 +9,120 @@ use crate::core::compiler::{BuildRunner, Unit};
99

1010
pub struct CompilationLock {
1111
/// The path to the lock file of the unit to compile
12-
unit: PathBuf,
12+
unit: UnitLock,
1313
/// The paths to lock files of the unit's dependencies
14-
dependency_units: Vec<PathBuf>,
14+
dependency_units: Vec<UnitLock>,
1515
}
1616

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

2121
let dependency_units = build_runner
2222
.unit_deps(unit)
2323
.into_iter()
24-
.map(|unit| build_runner.files().build_unit_lock(&unit.unit))
24+
.map(|unit| build_runner.files().build_unit_lock(&unit.unit).into())
2525
.collect_vec();
2626

2727
Self {
28-
unit: unit_path,
28+
unit: unit_lock,
2929
dependency_units,
3030
}
3131
}
3232

33-
pub fn lock(self) -> CompilationLockGuard {
34-
let unit_lock = OpenOptions::new()
35-
.create(true)
36-
.write(true)
37-
.append(true)
38-
.open(self.unit)
39-
.unwrap();
40-
unit_lock.lock().unwrap();
33+
pub fn lock(self) -> Self {
34+
let unit_lock = self.unit.lock_exclusive();
4135

4236
let dependency_locks = self
4337
.dependency_units
4438
.into_iter()
45-
.map(|d| {
46-
let f = OpenOptions::new()
47-
.create(true)
48-
.write(true)
49-
.append(true)
50-
.open(d)
51-
.unwrap();
52-
f.lock_shared().unwrap();
53-
f
54-
})
39+
.map(|d| d.lock_shared())
5540
.collect::<Vec<_>>();
5641

57-
CompilationLockGuard {
58-
_lock: unit_lock,
59-
_dependency_locks: dependency_locks,
42+
CompilationLock {
43+
unit: unit_lock,
44+
dependency_units: dependency_locks,
6045
}
6146
}
47+
48+
pub fn rmeta_produced(&self) {
49+
// Downgrade the lock on the unit we are building so that we can unblock other units to
50+
// compile. We do not need to downgrade our dependency locks since they should always be a
51+
// shared lock.
52+
self.unit.downgrade();
53+
}
54+
}
55+
56+
struct UnitLock {
57+
primary: PathBuf,
58+
secondary: PathBuf,
59+
gaurd: Option<UnitLockGuard>,
6260
}
6361

64-
pub struct CompilationLockGuard {
65-
_lock: File,
66-
_dependency_locks: Vec<File>,
62+
struct UnitLockGuard {
63+
primary: File,
64+
_secondary: Option<File>,
65+
}
66+
67+
impl UnitLock {
68+
pub fn lock_exclusive(self) -> UnitLock {
69+
let primary_lock = file_lock(&self.primary);
70+
primary_lock.lock().unwrap();
71+
72+
let secondary_lock = file_lock(&self.secondary);
73+
secondary_lock.lock().unwrap();
74+
75+
UnitLock {
76+
primary: self.primary,
77+
secondary: self.secondary,
78+
gaurd: Some(UnitLockGuard {
79+
primary: primary_lock,
80+
_secondary: Some(secondary_lock),
81+
}),
82+
}
83+
}
84+
85+
pub fn lock_shared(self) -> UnitLock {
86+
let primary_lock = file_lock(&self.primary);
87+
primary_lock.lock_shared().unwrap();
88+
89+
UnitLock {
90+
primary: self.primary,
91+
secondary: self.secondary,
92+
gaurd: Some(UnitLockGuard {
93+
primary: primary_lock,
94+
_secondary: None,
95+
}),
96+
}
97+
}
98+
99+
pub fn downgrade(&self) {
100+
let gaurd = self.gaurd.as_ref().unwrap();
101+
102+
// TODO: Add debug asserts to verify the lock state?
103+
104+
// We know we have an exclusive lock here so we should never block.
105+
// This is not super well documented but `lock_shared()` should downgrade the lock.
106+
// TODO: Need to validate on other platforms...
107+
gaurd.primary.lock_shared().unwrap();
108+
}
109+
}
110+
111+
fn file_lock<T: AsRef<Path>>(f: T) -> File {
112+
OpenOptions::new()
113+
.create(true)
114+
.write(true)
115+
.append(true)
116+
.open(f)
117+
.unwrap()
118+
}
119+
120+
impl From<(PathBuf, PathBuf)> for UnitLock {
121+
fn from(value: (PathBuf, PathBuf)) -> Self {
122+
Self {
123+
primary: value.0,
124+
secondary: value.1,
125+
gaurd: None,
126+
}
127+
}
67128
}

src/cargo/core/compiler/mod.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ fn rustc(
361361
};
362362

363363
return Ok(Work::new(move |state| {
364-
let _guard = lock.map(|v| v.lock());
364+
let mut lock = lock.map(|v| v.lock());
365365

366366
// Artifacts are in a different location than typical units,
367367
// hence we must assure the crate- and target-dependent
@@ -445,6 +445,7 @@ fn rustc(
445445
&manifest,
446446
&target,
447447
&mut output_options,
448+
lock.as_mut(),
448449
)
449450
},
450451
)
@@ -1021,6 +1022,7 @@ fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<W
10211022
&manifest,
10221023
&target,
10231024
&mut output_options,
1025+
None,
10241026
)
10251027
},
10261028
false,
@@ -2008,8 +2010,9 @@ fn on_stderr_line(
20082010
manifest: &ManifestErrorContext,
20092011
target: &Target,
20102012
options: &mut OutputOptions,
2013+
lock: Option<&mut CompilationLock>,
20112014
) -> CargoResult<()> {
2012-
if on_stderr_line_inner(state, line, package_id, manifest, target, options)? {
2015+
if on_stderr_line_inner(state, line, package_id, manifest, target, options, lock)? {
20132016
// Check if caching is enabled.
20142017
if let Some((path, cell)) = &mut options.cache_cell {
20152018
// Cache the output, which will be replayed later when Fresh.
@@ -2030,6 +2033,7 @@ fn on_stderr_line_inner(
20302033
manifest: &ManifestErrorContext,
20312034
target: &Target,
20322035
options: &mut OutputOptions,
2036+
lock: Option<&mut CompilationLock>,
20332037
) -> CargoResult<bool> {
20342038
// We primarily want to use this function to process JSON messages from
20352039
// rustc. The compiler should always print one JSON message per line, and
@@ -2265,6 +2269,10 @@ fn on_stderr_line_inner(
22652269
trace!("found directive from rustc: `{}`", artifact.artifact);
22662270
if artifact.artifact.ends_with(".rmeta") {
22672271
debug!("looks like metadata finished early!");
2272+
if let Some(lock) = lock {
2273+
println!("rmeta produced! downgrading lock!");
2274+
lock.rmeta_produced();
2275+
}
22682276
state.rmeta_produced();
22692277
}
22702278
return Ok(false);
@@ -2480,7 +2488,15 @@ fn replay_output_cache(
24802488
break;
24812489
}
24822490
let trimmed = line.trim_end_matches(&['\n', '\r'][..]);
2483-
on_stderr_line(state, trimmed, package_id, &manifest, &target, &mut options)?;
2491+
on_stderr_line(
2492+
state,
2493+
trimmed,
2494+
package_id,
2495+
&manifest,
2496+
&target,
2497+
&mut options,
2498+
None,
2499+
)?;
24842500
line.clear();
24852501
}
24862502
Ok(())

0 commit comments

Comments
 (0)