Skip to content

Commit 409fed7

Browse files
authored
Fix potential deadlock in CacheState::lock (#15698)
This PR fixes a potential source of deadlock in the `CacheState::lock` function ([here](https://github.com/smoelius/cargo/blob/84709f085062cbf3c51fa507527c1b2334015178/src/cargo/util/cache_lock.rs#L411)), as explained below. I ran into this deadlock while testing Dylint. For example, in [this GitHub run](https://github.com/trailofbits/dylint/actions/runs/15570922048), two jobs were killed after running for six hours. This fix seems to resolve the deadlock (e.g., see [this run](https://github.com/trailofbits/dylint/actions/runs/15822570315), which [uses the fix](https://github.com/trailofbits/dylint/actions/runs/15822570315/workflow#L119-L132)). Until this fix (or a similar one) appears in `rustup`-installable Cargo, is there an easy workaround? --- A `CacheState` struct holds [two recursive locks](https://github.com/smoelius/cargo/blob/84709f085062cbf3c51fa507527c1b2334015178/src/cargo/util/cache_lock.rs#L347-L357): `mutate_lock` and `cache_lock`. When `MutateExclusive` is passed to `CacheState::lock`, it tries to acquire both locks. First, it tries to acquire `mutate_lock`, then it tries to acquire `cache_lock`. The problematic case is when it acquires the first, but not the second. Note that if the second cannot be acquired because of an error, the `mutate_lock` recursive lock is decremented: https://github.com/rust-lang/cargo/blob/84709f085062cbf3c51fa507527c1b2334015178/src/cargo/util/cache_lock.rs#L412-L415 However, if the second would simply block, `LockingResult::WouldBlock` is returned. `CacheState::lock` is called from two places. One of those locations is in `CacheLocker::try_lock`:[^1] https://github.com/rust-lang/cargo/blob/84709f085062cbf3c51fa507527c1b2334015178/src/cargo/util/cache_lock.rs#L502-L506 Note that `CacheLocker::try_lock` creates a [`CacheLock`](https://github.com/rust-lang/cargo/blob/84709f085062cbf3c51fa507527c1b2334015178/src/cargo/util/cache_lock.rs#L427-L430) if and only if `LockingResult::LockAcquired` is returned. Furthermore, when a `CacheLock` is dropped, it decrements both `mutate_lock` and `cache_lock`: https://github.com/rust-lang/cargo/blob/84709f085062cbf3c51fa507527c1b2334015178/src/cargo/util/cache_lock.rs#L443-L446 A scan of `cache_lock.rs` shows that there are only three places[^2] where `mutate_lock.decrement()` is called: the error location in `CacheState::lock` (referenced above), and two places in `CacheLock::drop`. Thus, if `LockingResult::WouldBlock` is returned from `CacheState::lock`, `mutate_lock` is never decremented. [^1]: The other location is in `CacheLocker::lock`, which calls `CacheState::lock` with `BlockingMode::Blocking`. For that reason, `CacheLocker::lock` should not return `WouldBlock` when called from this location. [^2]: https://github.com/rust-lang/cargo/blob/84709f085062cbf3c51fa507527c1b2334015178/src/cargo/util/cache_lock.rs#L413, https://github.com/rust-lang/cargo/blob/84709f085062cbf3c51fa507527c1b2334015178/src/cargo/util/cache_lock.rs#L438, and https://github.com/rust-lang/cargo/blob/84709f085062cbf3c51fa507527c1b2334015178/src/cargo/util/cache_lock.rs#L445
2 parents f94409a + 0b362e3 commit 409fed7

File tree

1 file changed

+4
-1
lines changed

1 file changed

+4
-1
lines changed

src/cargo/util/cache_lock.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,10 @@ impl CacheState {
408408
.lock_exclusive(gctx, DOWNLOAD_EXCLUSIVE_DESCR, blocking)
409409
{
410410
Ok(LockAcquired) => {}
411-
Ok(WouldBlock) => return Ok(WouldBlock),
411+
Ok(WouldBlock) => {
412+
self.mutate_lock.decrement();
413+
return Ok(WouldBlock);
414+
}
412415
Err(e) => {
413416
self.mutate_lock.decrement();
414417
return Err(e);

0 commit comments

Comments
 (0)