Skip to content

Q049. How do deadlocks arise, and how can Rust code prevent them?

Short Answer

A deadlock occurs when participants wait forever for conditions that only one another can satisfy. The classic case is inconsistent lock order: one task holds A and waits for B while another holds B and waits for A. Rust prevents data races in safe code, but it does not prevent deadlocks. Use a global lock order, keep critical sections small, avoid awaiting or calling unknown code under a lock, and redesign toward one lock, partitioned ownership, or message passing where possible.

Deep Dive

The common necessary conditions are mutual exclusion, hold-and-wait, no forced preemption of a resource, and a circular wait. Breaking any condition prevents that deadlock class.

Practical controls include:

  • acquire multiple locks in one documented global order;
  • never recursively acquire a non-reentrant lock;
  • do not retain map guards while invoking methods that may lock the same shard;
  • use timeout or try_lock only as a recovery policy, not as proof of correctness;
  • avoid callbacks and .await while holding locks;
  • model state so one owner performs related transitions.

The borrow checker tracks memory references, not dynamic wait-for graphs. A program can therefore be memory-safe and permanently stuck.

Internal Model

Contended locks place threads or tasks in wait queues. In a cycle, no owner reaches the unlock operation that would wake another waiter.

Production diagnosis often uses thread dumps, async task instrumentation, lock metrics, and timeout logs. Tests should force different operation orderings; normal happy-path tests may never reproduce the cycle.

Example

rust
use std::sync::Mutex;

struct State {
    balances: Mutex<(i64, i64)>,
}

fn main() {
    let state = State {
        balances: Mutex::new((100, 100)),
    };

    let mut balances = state.balances.lock().unwrap();
    balances.0 -= 10;
    balances.1 += 10;
    assert_eq!(*balances, (90, 110));
}

Common Mistakes

  • Claiming safe Rust cannot deadlock.
  • Treating timeouts as a substitute for a consistent locking design.
  • Acquiring locks inside helpers without documenting their lock-order requirements.

Follow-ups

  • What is livelock, and how does it differ from deadlock?
  • How would you diagnose an intermittent production deadlock?
  • Can a single-threaded async task deadlock itself?

References

  • Rust Standard Library
  • Tokio Documentation
  • Linux kernel documentation