{"record":{"id":"4be726eefc62b94b","repo":"redis/redis-py","slug":"cannot-reacquire-an-unlocked-lock","errorCode":null,"errorMessage":"Cannot reacquire an unlocked lock","messagePattern":"Cannot reacquire an unlocked lock","errorType":"exception","errorClass":"LockError","httpStatus":null,"severity":"error","filePath":"redis/asyncio/lock.py","lineNumber":332,"sourceCode":"\n    async def do_extend(self, additional_time, replace_ttl) -> Literal[True]:\n        additional_time = int(additional_time * 1000)\n        if not bool(\n            await self.lua_extend(\n                keys=[self.name],\n                args=[self.local.token, additional_time, replace_ttl and \"1\" or \"0\"],\n                client=self.redis,\n            )\n        ):\n            raise LockNotOwnedError(\"Cannot extend a lock that's no longer owned\")\n        return True\n\n    def reacquire(self) -> Awaitable[Literal[True]]:\n        \"\"\"\n        Resets a TTL of an already acquired lock back to a timeout value.\n        \"\"\"\n        if self.local.token is None:\n            raise LockError(\"Cannot reacquire an unlocked lock\")\n        if self.timeout is None:\n            raise LockError(\"Cannot reacquire a lock with no timeout\")\n        return self.do_reacquire()\n\n    async def do_reacquire(self) -> Literal[True]:\n        timeout = int(self.timeout * 1000)\n        if not bool(\n            await self.lua_reacquire(\n                keys=[self.name], args=[self.local.token, timeout], client=self.redis\n            )\n        ):\n            raise LockNotOwnedError(\"Cannot reacquire a lock that's no longer owned\")\n        return True\n","sourceCodeStart":314,"sourceCodeEnd":346,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/asyncio/lock.py#L314-L346","documentation":"reacquire() resets the lock's TTL back to the original timeout; if the local token is None the lock is not held on this instance, so it raises LockError before contacting Redis. The same client-side guard as release/extend.","triggerScenarios":"Calling reacquire() before acquire(), after release(), or from a different thread/task than the acquirer with thread_local=True.","commonSituations":"Renewal logic run before/after ownership; cross-task token invisibility.","solutions":["Guard with 'if await lock.owned()' before reacquire.","Use thread_local=False for cross-task renewal.","Do not reacquire after release."],"exampleFix":"# before\nawait lock.reacquire()  # before any acquire -> LockError\n# after\nif await lock.owned():\n    await lock.reacquire()","handlingStrategy":"validation","validationCode":"if await lock.owned():\n    await lock.reacquire()","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Guard reacquire with 'await lock.owned()'.","Use thread_local=False for cross-task renewal."],"tags":["lock","distributed-lock","ownership","async"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}