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 isView on GitHub (pinned to bc6749cc3b)
Solutions
- Use the context manager: async with lock: ...
- If releasing manually, only release what you acquired: pair acquire() and release() in try/finally entered after successful acquire
- 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
- Prefer 'async with lock:' over manual acquire/release
- Enter try/finally only after acquire() succeeds
- Never release from a task that did not acquire
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
- cannot wait on un-acquired lock
- cannot notify on un-acquired lock
- BoundedSemaphore released too many times
- {self!r} is bound to a different event loop
- Failed to fetch glossary.json
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/4c708893001ad00c.
Report an issue: GitHub.