Reading a Linux Lockdep Report: Reconstruct the Dependency Cycle Before Changing Locks

Reconstruct the reported lock-class cycle before changing a lock or increasing a timeout. A circular-dependency warning describes acquisition orders that can combine into a deadlock. It need not mean the system reached that deadlock during the observed run. The useful result is a graph whose every edge has a source path, execution context and reproduction step.

This procedure is scoped to upstream Linux v6.12, ordinary exclusive-lock dependencies, and a recoverable development kernel. Reader/writer and recursive-read cases need the additional dependency rules in the same release. Vendor patches, PREEMPT_RT and architecture choices can change the applicable lock behavior. No target kernel or deliberately deadlocking workload was run for this article.

Start with the first complete warning

Preserve the warning header, attempted lock, already-held locks, previous dependency-chain stacks, current stack and surrounding activity. A screenshot containing only the current function omits the evidence that makes the cycle understandable. Keep the matching source, kernel configuration and module binaries with the record; resolve source locations before assigning responsibility.

The v6.12 locking-debug configuration makes CONFIG_PROVE_LOCKING select the dependency validator and associated checking. Verify the final configuration and any earlier message that disabled checking. A configuration fragment alone does not establish that the running image collected usable dependencies. Treat a run after validator exhaustion or disabled checks as a coverage problem.

Translate the warning into directed edges

The upstream lockdep design defines a dependency from one class to another when an acquisition is attempted while the first is held. Classes group logically equivalent locks; they are not simply individual addresses. Several device instances can therefore contribute to the same class history.

Use this expressly synthetic worksheet, rather than treating it as captured kernel output:

  • Previously observed: configuration path holds class A, then acquires class B. Record edge A → B.
  • Current report: removal path holds class B and attempts class A. Record candidate edge B → A.
  • Combined exclusive-lock graph: A → B → A. The current edge closes the cycle.

The following report-field worksheet uses paraphrased labels, fictional function names and no addresses. It is not a verbatim Linux log:

Paraphrased report field Synthetic contents Graph interpretation
Lock the current task wants Class A at remove_channel() Destination of the new edge
Lock the task already holds Class B, acquired earlier in remove_channel() Source of the new edge: B → A
Earlier acquisition history configure_channel() held A before taking B Previously observed edge: A → B
Current acquisition stack Removal caller → remove_channel() → acquisition of A Where to reproduce the closing edge
Resulting exclusive-lock dependency A → B → A Possible circular wait; not proof of a hang already reached

For each edge record the function that acquired the first lock, the function requesting the second, whether the first remains held, and the caller’s context. The report may print a dependency chain in a presentation order that is inconvenient for your drawing. Translate each actual hold-and-acquire relationship independently instead of copying line order into an arrow.

Two lock classes form a directed cycle: configuration observed A to B, and removal adds B to A. Separate boxes list edge evidence and repair checks.
Original synthetic lock-class graph. It represents dependency evidence, not measured timing or a captured report.

Check class identity before dismissing the result

Map A and B back to initialization sites, object types and all instances used by the test. Two printed addresses need not mean two independent classes. Conversely, the same field name in unrelated structures does not prove one shared class. Follow the source and class keys rather than relying on a shortened log label.

A legitimate hierarchy can require nested-lock annotations, such as consistently locking a parent before a child. The lockdep documentation provides subclass mechanisms for this situation. An annotation describes an existing invariant; it cannot make an inconsistent order correct. Before adding one, explain the hierarchy, prove that every entry path respects it, and test cases where the same role appears at several depths.

If a patch merely gives identical locks different classes to silence a warning, ask which real interleaving became impossible. If the answer is none, the patch has removed evidence rather than removed a deadlock. Include error unwinding and diagnostic callbacks in this review, since they often acquire a lock outside the expected happy-path order.

Keep interrupt usage on the graph

Record task, hard-interrupt and soft-interrupt context beside each acquisition. Lockdep also tracks whether relevant interrupts were enabled when a class was used. Its usage characters describe accumulated context history, not just the state of the task printing the warning. Decode them against the pinned document rather than memorizing a character from another kernel.

An interrupt-side acquisition can create a dependency even when there is only one apparent task caller. For example, a task may hold a lock and then be interrupted by a handler needing that lock. A class used in interrupt context also places restrictions on dependencies leading to interrupt-unsafe classes. The resulting report deserves a context analysis, not an automatic conversion of every lock into an IRQ-disabling variant.

For this article’s A/B example, keep both paths in task context so the two-edge explanation stays precise. If the actual report includes interrupt usage, extend the worksheet before selecting a repair. Do not use a sleepable mutex as a substitute for an interrupt-safe primitive without reviewing where it executes.

Reproduce component paths instead of forcing a hang

  1. Boot a known diagnostic image with a working recovery path and a complete report channel.
  2. Exercise the configuration operation that establishes A → B. Record the device generation and successful completion.
  3. Exercise the removal or error operation that establishes B → A. Preserve the first resulting report.
  4. Reduce unrelated traffic while keeping both acquisition paths. Mark any dependency that disappears when the reproducer is simplified.
  5. After correction, repeat both operations and their error exits, then add concurrent execution under the product’s supported workload.

Lockdep combines observed component sequences; a test does not have to schedule two threads into an actual permanent wait to establish this ordinary inversion. That distinction makes a bounded reproduction safer and easier to interpret. Do not create a forced hang on equipment controlling a live process merely to make the failure look more convincing.

Choose a repair that preserves the protected invariant

One candidate is a uniform order: both operations acquire A before B. Another is removing the nesting by taking a stable snapshot, releasing the first lock, then performing the second operation. The latter needs a lifetime and revalidation rule because state may change between the two critical sections. Neither change is valid solely because the warning disappears.

Consider a hypothetical driver where configuration protects a channel list with A and a channel state with B. Removal currently takes B before deleting the entry under A. Review whether removal can first mark the channel unavailable under the agreed order, then finish slow cleanup after releasing both. Check every callback and failure return against that proposal; do not release a lock early while leaving half-updated shared state visible.

Close with a dependency contract

Deliver the two source-backed edges, class identities, context annotations, selected invariant and regression matrix. A corrected run should exercise the previously conflicting paths with checking still active and show the intended state transitions. Also verify functional behavior and resource cleanup, which lockdep does not establish.

A quiet run excludes neither an unvisited acquisition path nor an unmodeled lifetime bug. Reader/writer cases, intentional nesting and unsupported instrumentation require their own review. The completion criterion is an explained locking contract with exercised evidence, not a larger timeout or an empty console.

Similar Posts