Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reading a Deadlock Report

The callback receives a DeadlockInfo value. Start with source: it decides whether the report describes a blocked cycle now or an ordering risk observed earlier.

  • DeadlockSource::WaitForGraph is an active, validated wait-for cycle. Treat it as an incident: the listed threads are blocked on incompatible owners in the detector’s current snapshot.
  • DeadlockSource::LockOrderViolation is a potential lock-order violation. It says executions have established a circular ordering rule, not that those threads are currently stuck. Reproduce and decide whether the paths can overlap before escalating it as an outage.

Field-by-field procedure

FieldWhat it containsHow to use it
sourceThe detector that emitted the finding.Always inspect first; it determines the confidence and triage path.
thread_cycleOrdered thread IDs. For an active wait-for report, each thread waits on the next thread and the final thread waits on the first. For a lock-order report it is only the thread that completed the suspicious acquisition.Use it to attach request IDs, worker names, and stack captures. It is primary evidence only for WaitForGraph.
thread_waiting_for_locks(thread_id, lock_id) pairs for the attempted acquisition that led to the report.For each cycle thread, locate that lock wrapper and confirm which guard is still live. This is the most direct route from IDs to source sites.
lock_order_cycleNone for a wait-for report; the ordered lock cycle for a lock-order violation.Source-specific evidence. Read A, B, C, A as “A was held before B, B before C, and C before A” across observed acquisitions. Do not read it as a set of currently blocked locks.
timestampAn ISO-8601 detection timestamp.Correlate with request logs, deploys, and traces. It timestamps observation, not necessarily the first moment an application stopped making progress.
verification_requestOptional (lock_id, thread_id) verification metadata.Preserve it verbatim for tooling. Current normal report paths leave it None; do not require it to diagnose either source.

The ordered cycle is deliberately directional. Given thread_cycle: [101, 202] and thread_waiting_for_locks: [(101, 17), (202, 42)], investigate: thread 101 is waiting for 17, held incompatibly by 202; thread 202 is waiting for 42, held incompatibly by 101. The same list is not a claim that the IDs are sorted, or that every holder of a shared lock must be in the cycle.

Example: active wait-for report

source: WaitForGraph
thread_cycle: [101, 202]
thread_waiting_for_locks: [(101, 17), (202, 42)]
lock_order_cycle: None
timestamp: "2026-07-29T09:41:12.807Z"
verification_request: None

This is an active deadlock report. Capture the two thread stacks, then search the lock construction sites for IDs 17 and 42 in the optional event log. The likely shape is 101 holding 42 while requesting 17, and 202 holding 17 while requesting 42. Fix the overlapping critical sections or establish one acquisition order; see two-Mutex inversion.

Example: potential lock-order report

source: LockOrderViolation
thread_cycle: [202]
thread_waiting_for_locks: [(202, 17)]
lock_order_cycle: Some([17, 42, 17])
timestamp: "2026-07-29T09:48:33.042Z"
verification_request: None

This is not proof that 202 is blocked. It means prior execution recorded 17 before 42, while this acquisition, holding 42 and requesting 17, closes the historical cycle. Inspect both paths, determine whether they run concurrently and share the same lock instances, then use the lock-order workflow. If the overlap is real, use stress testing to seek an active WaitForGraph report; absence of one in a finite run does not make the ordering safe.

Keep the callback small

Register a handler with Deloxide::callback, but treat it as an alert handoff, not a recovery transaction. Deloxide dispatches callbacks away from the detecting thread, yet a callback that waits on application locks, performs slow network I/O, or invokes a complex shutdown path can still delay later notifications or compound an incident. Move DeadlockInfo into a bounded incident queue with a nonblocking send, record overload without taking an application lock, and return.

#![allow(unused)]
fn main() {
extern crate deloxide;
use deloxide::{DeadlockInfo, Deloxide};
use std::sync::{
    Arc,
    atomic::{AtomicU64, Ordering},
    mpsc,
};

let (reports_tx, reports_rx) = mpsc::sync_channel::<DeadlockInfo>(64);
let dropped_reports = Arc::new(AtomicU64::new(0));
let callback_drops = Arc::clone(&dropped_reports);
Deloxide::new()
    .callback(move |info| {
        if reports_tx.try_send(info).is_err() {
            // Counts a full queue or disconnected receiver without blocking.
            callback_drops.fetch_add(1, Ordering::Relaxed);
        }
    })
    .start()
    .expect("detector initialization");

// A separate supervisory task can correlate, persist, or page on reports_rx.
let _ = (reports_rx, dropped_reports);
}

try_send deliberately applies no backpressure to the callback. If the queue is full or its receiver has gone away, this example drops that report and increments a counter; production alerting should monitor the counter and size the queue for its incident burst budget. The supervisor may perform slower persistence and network work, provided it does not need an implicated application lock.

If logging is enabled, Deloxide::with_log and showcase_this can provide the event history. The structured callback payload remains the authoritative alert input; the visualization is supporting evidence.