{"record":{"id":"4c708893001ad00c","repo":"python/cpython","slug":"lock-is-not-acquired","errorCode":null,"errorMessage":"Lock is not acquired.","messagePattern":"Lock is not acquired\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"Lib/asyncio/locks.py","lineNumber":142,"sourceCode":"        self._locked = True\n        return True\n\n    def release(self):\n        \"\"\"Release a lock.\n\n        When the lock is locked, reset it to unlocked, and return.\n        If any other tasks are blocked waiting for the lock to become\n        unlocked, allow exactly one of them to proceed.\n\n        When invoked on an unlocked lock, a RuntimeError is raised.\n\n        There is no return value.\n        \"\"\"\n        if self._locked:\n            self._locked = False\n            self._wake_up_first()\n        else:\n            raise RuntimeError('Lock is not acquired.')\n\n    def _wake_up_first(self):\n        \"\"\"Ensure that the first waiter will wake up.\"\"\"\n        if not self._waiters:\n            return\n        fut = next(iter(self._waiters))\n\n        # .done() means that the waiter is already set to wake up.\n        if not fut.done():\n            fut.set_result(True)\n\n\nclass Event(mixins._LoopBoundMixin):\n    \"\"\"Asynchronous equivalent to threading.Event.\n\n    Class implementing event objects.  An event manages a flag that can be\n    set to true with the set() method and reset to false with the clear()\n    method.  The wait() method blocks until the flag is true.  The flag is","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/asyncio/locks.py#L124-L160","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nawait lock.acquire()\ntry:\n    ...\nfinally:\n    lock.release()\n    lock.release()  # double release -> RuntimeError\n\n# after\nasync with lock:\n    ...","handlingStrategy":"validation","validationCode":"if not lock.locked():\n    # releasing now would raise RuntimeError; nothing to release","typeGuard":null,"tryCatchPattern":"try:\n    lock.release()\nexcept RuntimeError:\n    pass  # was not held; fix the imbalance at the source","preventionTips":["Prefer 'async with lock:' over manual acquire/release","Enter try/finally only after acquire() succeeds","Never release from a task that did not acquire"],"tags":["asyncio","lock","synchronization"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}