I got E08255 UNPROVABLE_LOCK_ORDER — why are two locks of the same type rejected even with no reverse nesting?

← Concurrency · Ref: Q1352

E08255 fires when two locks of the SAME type — held on caller-supplied objects — are nested, even if only ONE nesting direction appears in the code. Because the two objects (`from` and `to` here) are interchangeable at the call site, a single static `from` then `to` ordering can instantiate at runtime as both `A,B` and `B,A`. The compiler cannot PROVE a consistent global order, so it rejects the nesting.

WHAT THE ERROR MEANS

Unlike E08252 (two distinct lock fields, provably orderable), here both locks are `theLock` reached through same-type parameters. `moveFunds(x, y)` and `moveFunds(y, x)` both type-check, so nesting `to` inside `from` is simultaneously both acquisition orders — the classic bank-transfer / dining-philosophers deadlock.

HOW TO FIX

Don't nest same-type locks. Acquire them one at a time, or put the shared payload under a SINGLE MutexLock of an owning record so only one lock is ever taken.

  require from.getLock().enter(fromKey)   //completes and releases
  require to.getLock().enter(toKey)        //separate, unnested

WHY EK9 DETECTS THIS

Same-type nested locks are the textbook deadlock (two accounts, two philosophers). Runtime ordering tricks (lock by hashcode) are error-prone; EK9 rejects the shape at compile time.

Example

defines module qa.concurrency.unprovable.lock.order

  defines class

    Vault
      theLock as MutexLock of Integer: MutexLock(Integer(0))
      getLock() as pure
        <- rtn as MutexLock of Integer: theLock
      default operator ?

  defines function

    moveFunds()
      ->
        from as Vault
        to as Vault
      fromKey <- () is MutexKey of Integer as function
        stdout <- Stdout()
        stdout.println(lockedItem)
      require from.getLock().enter(fromKey)

      toKey <- () is MutexKey of Integer as function
        stdout <- Stdout()
        stdout.println(lockedItem)
      require to.getLock().enter(toKey)

Common mistakes

E08255 — Both locks are `theLock` reached through same-type params `from`/`to`, which are interchangeable at the call site, so nesting `to` inside `from` instantiates as both orders at runtime — an unprovable order. Fix by acquiring sequentially or unifying under one MutexLock of a record. See ek9 -h E08255.

Incorrect:

innerKey <- () is MutexKey of Integer as function
        stdout <- Stdout()
        stdout.println(lockedItem)
      outerKey <- (to, innerKey) is MutexKey of Integer as function
        require to.getLock().enter(innerKey)
      require from.getLock().enter(outerKey)

Correct:

fromKey <- () is MutexKey of Integer as function
        stdout <- Stdout()
        stdout.println(lockedItem)
      require from.getLock().enter(fromKey)

      toKey <- () is MutexKey of Integer as function
        stdout <- Stdout()
        stdout.println(lockedItem)
      require to.getLock().enter(toKey)
Other ways to ask this
  • Why does EK9 reject nesting two same-type locks?
  • E08255 unprovable lock order fix
  • bank transfer from.getLock()/to.getLock() deadlock EK9
  • two instances same lock type nested EK9

Coming from another language?

Java: the canonical fix is to lock by System.identityHashCode ordering — easy to get wrong, invisible if wrong. EK9: nesting two same-type locks reached via interchangeable objects is a compile-time error (E08255); unify under one lock or acquire sequentially.

Keywords: type, deadlock, order, instance, philosophers, lock, same, transfer, nested, MutexLock, concurrent, unprovable, E08255