Programming & Compilers
Lock ordering and deadlock: ranks and callback waits
Lock ordering and deadlock depend on covered waits. Compare circular wait, a resource acquisition hierarchy, callback completion and state revalidation.

Two threads can block each other when each holds an exclusive lock that the other needs. A shared strict acquisition order excludes that opposing-lock cycle. A callback-completion wait adds a different dependency, so its resource requirements belong in the graph before the same conclusion can be applied.
An analysis of lock ordering and deadlock must therefore identify the resources and waits included in its proof. Consistent ranks are useful only when the actual acquisition paths follow them and the relevant dependency graph matches the model.
The examples use hypothetical threads and resources. They examine wait relationships rather than priority donation, thread-pool capacity or a particular language's locking API.
Construct circular wait with opposing acquisitions
Let locks A and B be exclusive, and let threads T1 and T2 each need both. Initially T1 acquires A while T2 acquires B. Each lock is held by a different thread.
T1 next requests B without releasing A. T2 requests A without releasing B. Under the stipulated blocking behaviour, T1 cannot continue until T2 releases B, while T2 cannot continue until T1 releases A.
The wait conditions form a cycle. T1 waits for a resource owned by T2, and T2 waits for a resource owned by T1. Assume neither operation has an independent permitted path that releases its held lock before that wait is satisfied.
Those conditions establish the deadlock in this model. Simply observing two requests for locks would not do so; ownership, blocked state and the condition for resumption are part of the evidence.
The structured-concurrency guide supplies a completion-boundary comparison for circular wait. Its parent must account for actual child settlement before releasing resources that a child is still permitted to use. That is a lifetime contract, not proof of a lock cycle. When applying a completion wait to our lock example, add the awaited child and the lock it needs; waiting for settlement while retaining that lock can create the additional edge analysed below.
A resource acquisition hierarchy gives the cycle a contradiction
Assign A rank 10 and B rank 20. Require every participant that acquires several covered locks to acquire them only in strictly increasing rank order while holding earlier locks.
T1 can hold A and then request B. T2 cannot hold B and then request A under that rule. It must follow the same A-before-B order for this lock set. The opposing-acquisition history is excluded.
For a general covered cycle, suppose holding lock L1 while waiting for L2 requires rank(L1) less than rank(L2). Each next edge requires another strict increase. Returning to L1 would require its rank to be greater than itself.
A strict order cannot return through successive increases to its starting rank. This excludes cycles consisting entirely of the covered lock-acquisition dependencies, provided every participant enforces the rule. The contradiction applies to resource requests made while retaining earlier resources, rather than to arbitrary completion edges.
The argument uses one shared order, exclusive ownership and the stated wait semantics. Different modules cannot assign contradictory ranks to the same resources and still claim the same argument.
The runtime-code-replacement guide supplies an access-coverage comparison for a resource acquisition hierarchy. Its migration example serializes conversion but still has to account for old frames and retained callbacks that can access state. Likewise, our hierarchy proof needs every participating acquisition path to follow the common order. The replacement guide does not provide that lock hierarchy or prove absence of deadlock; it identifies access relationships that a narrowly scoped discipline can otherwise omit.
Strict ordering needs a rule for equal ranks
Suppose A and B both receive rank 10, and the implementation permits nondecreasing acquisitions. T1 can hold A and request B while T2 holds B and requests A. Both requests meet the nondecreasing test.
With equal ranks, the required strict-increase contradiction is unavailable. Equal ranks left the problematic pair unordered. A numeric label on every lock is therefore insufficient if the comparison still permits opposing acquisitions within one rank.
A model can establish a stable tie-breaking order among distinct covered resources, or require a rule that forbids acquiring such a second lock while holding the first. Whichever rule it uses must cover the actual pairs.
The identity used for that order must be consistent for the resource lifetime relevant to the proof. A tie-breaker interpreted differently by two components does not supply a common hierarchy.
Reacquiring the same lock also needs defined semantics. If the lock is nonreentrant and the holder blocks on acquiring it again, a self-dependency can arise. A hierarchy proof cannot treat equality as a harmless second distinct acquisition without examining that case.
Dynamic lock sets must still respect the same order
Some operations discover which resources they need only after reading state. The hierarchy remains applicable, but the operation must obtain its required locks in a sequence consistent with the shared order.
For a known set with ranks 10, 20 and 30, arranging acquisitions as 10 then 20 then 30 fits the rule. Sorting only a subset and later adding a lower-ranked resource while retaining a higher-ranked one does not.
Suppose an operation already holds ranks 10 and 30, then discovers it needs rank 20. Requesting 20 at that point violates the stated acquisition rule even though all three locks have sensible individual ranks.
The design needs a path that restores admissible ordering, such as releasing and re-establishing the required protected state under a complete ordered set. That path must also address any assumptions invalidated during release.
The hierarchy argument does not authorize ignoring the newly discovered dependency or silently treating its request as permitted. Record the actual held set and requested resource when checking a dynamic path.
A callback can add an unranked dependency
Now use only lock A. T1 holds A and invokes an external callback that executes as T2. T1 waits for that callback to complete before it releases A.
The callback needs A before it can finish, so T2 waits for T1's lock. T1 in turn waits for T2's completion. This creates a cycle even though no thread acquired B before A or violated a two-lock rank order.
The completion dependency is outside the initial lock-acquisition graph. The rank proof excluded cycles made only of covered acquisition edges; this cycle contains another kind of wait.
Record completion C as an event that T2 must satisfy before T1 resumes. The dependency chain is T1 waits for C, C requires T2 to finish, T2 waits for A, and A is owned by T1. T1 retains A until C. Those simultaneous conditions close the cycle without any opposing pair of ranked-lock acquisitions.
Other completion waits can have the same role when the awaited work requires a resource held by the waiter. The analysis must identify that requirement rather than assume every external call is independent.
Extending the hierarchy to another dependency requires a defined ordering argument for that dependency's actual wait behaviour. Assigning a rank after the cycle has already been drawn does not establish that the execution enforced it.
Release before the external call, then revalidate state
One possible protocol releases A before waiting for the callback. In the stipulated example, T2 can then acquire A, complete its permitted work and release it. T1 can wait without retaining the resource the callback needs.
While T1 waits for C in that release protocol, A is available for T2 to acquire and release. The edge from A back to T1 is absent at this checkpoint, so the identified callback cycle is broken. Other dependencies remain subject to their own wait and ownership conditions.
Releasing A also allows shared state to change. Suppose T1 read a resource as available while holding A, then released the lock and waited. Another operation can consume that resource before T1 reacquires A.
For a concrete state variant, suppose the protected remaining count was one when T1 inspected it. While A is released, the callback consumes that last unit and leaves the count zero. After reacquisition, T1's old local value still says one, but the current protected state says zero. Revalidation must use the current value before admitting T1's update under this rule. This history completes the callback without legitimizing a second consumption.
T1 cannot use the earlier availability decision as if protection continued through the external call. It must revalidate the state required for its intended update or follow another protocol that preserves the necessary condition.
A version or reservation mechanism could supply a specified relationship, but its meaning and enforcement need evidence. Merely reacquiring A proves current exclusion, not that the protected values still equal the earlier snapshot.
Releasing before the callback changes the wait graph and the state-transition assumptions. Preventing the callback deadlock while applying an outdated decision would replace one defect with another.
Review every wait while a resource remains held
For each path, list held covered locks, requested locks and the shared rank relation. Include equal-rank decisions, dynamic additions and any attempt to acquire the same nonreentrant resource again.
Then list completion waits, callbacks and other dependencies active while those resources remain held. Identify who can satisfy each wait and what resources that work needs. The graph must represent the actual resumption conditions.
A lock-only cycle proof applies to its covered subgraph. If an excluded edge closes a cycle, retain it as a distinct failure rather than reporting the numeric hierarchy as proof that no deadlock exists.
For a release-before-call change, check the specific callback history and the later state revalidation. The first establishes removal of the covered wait cycle; the second checks the validity of the resumed transition.
Keep the actual held-lock record separate from the proposed rank table, then compare every requested resource with the locks retained at that moment. The Lenovo IdeaPad 15.6-inch FHD touch laptop listing pairs a 15.6-inch FHD touchscreen with 16GB DDR4 memory, and identifies a Core i5-1334U processor and 512GB SSD. The laptop provides a computing workspace for viewing the two records and retaining their files. The simultaneous ownership and wait relationships supply the cycle evidence.
Inspect the callback-completion edge beside its required resource, and retain the current protected count alongside any earlier local snapshot. The ASUS Vivobook 14 FHD laptop catalogue describes a Core 5 120U processor and a 14-inch FHD display, with 16GB DDR4 memory. Its workspace identity can accompany a wait-graph and revalidation table. The table still needs the actual callback prerequisite and the value observed after reacquiring A.
For A ranked 10 and B ranked 20, the enforced common order excludes holding B while requesting A. For the callback, T1 retaining A while awaiting T2 still closes a completion-and-lock cycle. Releasing A lets that callback finish in the supplied history, but T1 then sees a current count of zero and cannot consume the unit remembered in its earlier snapshot.