Skip to content

Commit f2496ee

Browse files
committed
Auto merge of #9677 - ehuss:serialize-fix, r=alexcrichton
Serialize `cargo fix` This changes `cargo fix` so that it only fixes one crate at a time. Previously, it would allow concurrent fixing across packages. This caused a problem if a workspace uses things like `#[path]` or `include!()` of a shared file between packages. A real-world example of this is serde, which has [this](https://github.com/serde-rs/serde/blob/9c39115f827170f7adbdfa4115f5916c5979393c/serde_derive_internals/lib.rs#L43-L45) which reuses the some source files between the `serde` and `serde_derive` packages. Modifying those files concurrently causes corruption and the fix will fail. Closes #6528
2 parents f4e1110 + c8e4f8e commit f2496ee

File tree

2 files changed

+53
-4
lines changed

2 files changed

+53
-4
lines changed

src/cargo/ops/fix.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -420,10 +420,12 @@ fn rustfix_crate(
420420
// process at a time. If two invocations concurrently check a crate then
421421
// it's likely to corrupt it.
422422
//
423-
// We currently do this by assigning the name on our lock to the manifest
424-
// directory.
425-
let dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is missing?");
426-
let _lock = LockServerClient::lock(&lock_addr.parse()?, dir)?;
423+
// Historically this used per-source-file locking, then per-package
424+
// locking. It now uses a single, global lock as some users do things like
425+
// #[path] or include!() of shared files between packages. Serializing
426+
// makes it slower, but is the only safe way to prevent concurrent
427+
// modification.
428+
let _lock = LockServerClient::lock(&lock_addr.parse()?, "global")?;
427429

428430
// Next up, this is a bit suspicious, but we *iteratively* execute rustc and
429431
// collect suggestions to feed to rustfix. Once we hit our limit of times to

tests/testsuite/fix.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Tests for the `cargo fix` command.
22
33
use cargo::core::Edition;
4+
use cargo_test_support::compare::assert_match_exact;
45
use cargo_test_support::git;
56
use cargo_test_support::paths::CargoPathExt;
67
use cargo_test_support::registry::{Dependency, Package};
@@ -1552,3 +1553,49 @@ fn fix_edition_2021() {
15521553
.run();
15531554
assert!(p.read_file("src/lib.rs").contains(r#"0..=100 => true,"#));
15541555
}
1556+
1557+
#[cargo_test]
1558+
fn fix_shared_cross_workspace() {
1559+
// Fixing a file that is shared between multiple packages in the same workspace.
1560+
// Make sure two processes don't try to fix the same file at the same time.
1561+
let p = project()
1562+
.file(
1563+
"Cargo.toml",
1564+
r#"
1565+
[workspace]
1566+
members = ["foo", "bar"]
1567+
"#,
1568+
)
1569+
.file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0"))
1570+
.file("foo/src/lib.rs", "pub mod shared;")
1571+
// This will fix both unused and bare trait.
1572+
.file("foo/src/shared.rs", "pub fn fixme(x: Box<&Fn() -> ()>) {}")
1573+
.file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0"))
1574+
.file(
1575+
"bar/src/lib.rs",
1576+
r#"
1577+
#[path="../../foo/src/shared.rs"]
1578+
pub mod shared;
1579+
"#,
1580+
)
1581+
.build();
1582+
1583+
// The output here can be either of these two, depending on who runs first:
1584+
// [FIXED] bar/src/../../foo/src/shared.rs (2 fixes)
1585+
// [FIXED] foo/src/shared.rs (2 fixes)
1586+
p.cargo("fix --allow-no-vcs")
1587+
.with_stderr_unordered(
1588+
"\
1589+
[CHECKING] foo v0.1.0 [..]
1590+
[CHECKING] bar v0.1.0 [..]
1591+
[FIXED] [..]foo/src/shared.rs (2 fixes)
1592+
[FINISHED] [..]
1593+
",
1594+
)
1595+
.run();
1596+
1597+
assert_match_exact(
1598+
"pub fn fixme(_x: Box<&dyn Fn() -> ()>) {}",
1599+
&p.read_file("foo/src/shared.rs"),
1600+
);
1601+
}

0 commit comments

Comments
 (0)