python/cpython · error · RuntimeError

Lock is not acquired.

Error message

Lock is not acquired.

What it means

asyncio.Lock.release() raises RuntimeError when the lock is already unlocked. Unlike threading.Lock (where release() on an unlocked lock is an error too but surfaces as a different failure), asyncio makes this explicit: releasing a lock you never acquired breaks the one-waker wakeup discipline (_wake_up_first).

Source

Thrown at Lib/asyncio/locks.py:142

        self._locked = True
        return True

    def release(self):
        """Release a lock.

        When the lock is locked, reset it to unlocked, and return.
        If any other tasks are blocked waiting for the lock to become
        unlocked, allow exactly one of them to proceed.

        When invoked on an unlocked lock, a RuntimeError is raised.

        There is no return value.
        """
        if self._locked:
            self._locked = False
            self._wake_up_first()
        else:
            raise RuntimeError('Lock is not acquired.')

    def _wake_up_first(self):
        """Ensure that the first waiter will wake up."""
        if not self._waiters:
            return
        fut = next(iter(self._waiters))

        # .done() means that the waiter is already set to wake up.
        if not fut.done():
            fut.set_result(True)


class Event(mixins._LoopBoundMixin):
    """Asynchronous equivalent to threading.Event.

    Class implementing event objects.  An event manages a flag that can be
    set to true with the set() method and reset to false with the clear()
    method.  The wait() method blocks until the flag is true.  The flag is

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Use the context manager: async with lock: ...
  2. If releasing manually, only release what you acquired: pair acquire() and release() in try/finally entered after successful acquire
  3. Check lock.locked() before a defensive release only if ownership is certain

Example fix

# before
await lock.acquire()
try:
    ...
finally:
    lock.release()
    lock.release()  # double release -> RuntimeError

# after
async with lock:
    ...
Defensive patterns

Strategy: validation

Validate before calling

if not lock.locked():
    # releasing now would raise RuntimeError; nothing to release

Try / catch

try:
    lock.release()
except RuntimeError:
    pass  # was not held; fix the imbalance at the source

Prevention

When it happens

Trigger: Calling lock.release() without a matching await lock.acquire(); using lock.release() in a finally block when acquire() raised or was skipped; releasing from a different task than the owner after timeout paths.

Common situations: Manual acquire/release instead of 'async with lock'; finally: lock.release() where the body never acquired due to an early exception; refactors that duplicated the release call; cancellation between acquire success and the try block.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/4c708893001ad00c. Report an issue: GitHub.