I got E08252 NESTED_ENTER_DIFFERENT_LOCK — how does nesting two locks form a deadlock cycle?
← Concurrency · Ref: Q1351
E08252 fires when EK9's deadlock detector finds two code paths that nest the same pair of distinct locks in OPPOSITE orders — one acquires lockA then lockB, another acquires lockB then lockA. Two threads running these paths concurrently can each hold one lock and wait forever for the other.
WHAT THE ERROR MEANS
Every `lockA.enter(...)` with a `lockB.enter(...)` nested inside records an edge lockA -> lockB in a workspace-wide precedence graph. When updateA nests getB() inside getA() (edge A->B) and updateB nests getA() inside getB() (edge B->A), the graph has a cycle A->B->A — a deadlock.
HOW TO FIX
Never hold two locks at once when the acquisition order can contradict. Acquire them one at a time (release the first before taking the second), or unify the protected state under a single MutexLock of a record.
require pair.getA().enter(keyA) //completes and releases require pair.getB().enter(keyB) //independent — no nesting
WHY EK9 DETECTS THIS
Lock-order cycles only deadlock under specific interleavings, so they slip through testing. EK9 builds the precedence graph at compile time (the model the Linux kernel's lockdep uses at runtime) and refuses to compile any cycle.
Example
defines module qa.concurrency.nested.enter.different defines class LockPair lockA as MutexLock of Integer: MutexLock(Integer(0)) lockB as MutexLock of Integer: MutexLock(Integer(0)) getA() <- rtn as MutexLock of Integer: lockA getB() <- rtn as MutexLock of Integer: lockB default operator ? defines function updateA() -> pair as LockPair keyA <- () is MutexKey of Integer as function stdout <- Stdout() stdout.println(lockedItem) require pair.getA().enter(keyA) updateB() -> pair as LockPair keyB <- () is MutexKey of Integer as function stdout <- Stdout() stdout.println(lockedItem) require pair.getB().enter(keyB)
Common mistakes
E08252 — updateA nests getB() inside getA() (edge A->B) while updateB nests getA() inside getB() (edge B->A), forming a cycle A->B->A — a runtime deadlock. Fix by acquiring the locks sequentially (no nesting) or unifying them under one MutexLock of a record. See ek9 -h E08252.
Incorrect:
updateA()
-> pair as LockPair
innerA <- () is MutexKey of Integer as function
stdout <- Stdout()
stdout.println(lockedItem)
outerA <- (pair, innerA) is MutexKey of Integer as function
require pair.getB().enter(innerA)
require pair.getA().enter(outerA)
updateB()
-> pair as LockPair
innerB <- () is MutexKey of Integer as function
stdout <- Stdout()
stdout.println(lockedItem)
outerB <- (pair, innerB) is MutexKey of Integer as function
require pair.getA().enter(innerB)
require pair.getB().enter(outerB)
Correct:
updateA()
-> pair as LockPair
keyA <- () is MutexKey of Integer as function
stdout <- Stdout()
stdout.println(lockedItem)
require pair.getA().enter(keyA)
updateB()
-> pair as LockPair
keyB <- () is MutexKey of Integer as function
stdout <- Stdout()
stdout.println(lockedItem)
require pair.getB().enter(keyB)
Other ways to ask this
- Why does nesting two MutexLocks fail in EK9?
- E08252 nested enter different lock fix
- lock-order cycle from getA/getB nesting
- two locks acquired in opposite order EK9
Coming from another language?
Java/C#: lock-order deadlocks are found at runtime via thread dumps; static tools catch only simple cases. Go: the runtime detector fires only on total deadlock. EK9: workspace-wide static cycle detection on the lock precedence graph, rejected at compile time.
Keywords: lock, enter, order, cycle, E08252, deadlock, concurrent, precedence, getB, getA, MutexLock, nested