-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Description
rust-analyzer version: 0.3.2441-standalone (d8887c0758 2025-04-26)
rustc version: `rustc 1.85.1 (4eb161250 2025-03-15)
editor or extension: Cursor v0.49.6 based on VSCode 1.96.2 + official rust-lang.rust-analyzer extension version 0.3.2441
relevant settings: VSCode workspace settings file containing
"rust-analyzer.cargo.extraArgs": ["--frozen"],
"rust-analyzer.cargo.extraEnv": { "CARGO_NET_OFFLINE": "true" },
"rust-analyzer.cargo.targetDir": true,
What I expected to happen: r-a cannot cause Cargo.lock changes, due to "rust-analyzer.cargo.extraArgs": ["--frozen"]
What actually happens: r-a occasionally causes Cargo.lock changes, due to a cargo metadata call that doesn't supply --frozen
How I verified this is from r-a and not something else:
- replaced
cargowith a shim that logs full invocations - this recorded an r-a invocation of
cargo metadata --format-version 1 --manifest-path /path/to/Cargo.toml --filter-platform <my-platform>— note this did not include the configured--frozen - git then reported changes in
Cargo.lock
How this happens in the real world
In the real-world this happens due to a "torn read" race condition versus git checkout. I'll describe the real-world situation first for intuition-building purposes, then offer a way to simulate the outcome of the race for easier debugging.
Consider a large repo where a git checkout may take a noticeable amount of time. Say we move from a newer branch to an older one, where the newer branch had updated some dependencies and picked up new ones.
git checkout <older-branch>- mid-checkout, imagine
Cargo.tomlbecomes updated but the matchingCargo.lockchange isn't applied yet — git is busy updating other files and hasn't gotten to it yet - at this instant, the VSCode extension runs r-a
- r-a runs
cargo metadatawithout--frozen, despite the configuration (as mentioned above) - (not positive about this, but possible)
"rust-analyzer.cargo.targetDir": truemeans the lockfile isn't locked during this process cargo metadatareadsCargo.tomlandCargo.lock, and continues running (A)- while
cargo metadatais in flight, the git checkout updatesCargo.lock(B) cargonotices that it's getting a different resolution: some of the dependencies present in theCargo.lockit read at (A) are superfluous, so it rewritesCargo.lockto exclude them — but it leaves the updated versions of other still-used dependencies (C)- (C) happens after (B), so git's checkout of
Cargo.lockat (B) is overwritten by (C)
At this point, both the checkout and the r-a run are complete. A subsequent git status shows Cargo.lock has newer versions of some dependencies — a hybrid lockfile between the newer and older branch.
Simulating the torn read
The easiest way to simulate the race is:
git checkout -b older-branch
git checkout -b newer-branch
cargo update # to cause existing dependencies' versions to be updated to newer versions
cargo add <some-new-dependency>
git commit -m "lockfile bump"
# switch to the older branch but check out the newer branch's lockfile
git checkout older-branch
git checkout newer-branch Cargo.lock
# at this point, we've set up the "torn read" condition manually;
# cargo commands like `cargo check` or `cargo metadata` will update
# `Cargo.lock` to remove `<some-new-dependency>` from the lockfile
# but will *preserve* the `cargo update` outcome otherwise,
# creating a new `Cargo.lock` that is unlike either branch's contents
r-a origin of the cargo metadata call
AFAICT the cargo metadata invocation in question is coming from this block, which explicitly passes locked: false. There's a similar block above it, for the rustc_dir case when there's a custom sysroot, which might also be similarly affected — I'm not sure.
Happy to provide any more info or help however I can. Thanks for looking into this!