{"record":{"id":"d27f31b0ce580e68","repo":"redis/redis-py","slug":"cannot-extend-a-lock-that-s-no-longer-owned","errorCode":null,"errorMessage":"Cannot extend a lock that's no longer owned","messagePattern":"Cannot extend a lock that's no longer owned","errorType":"exception","errorClass":"LockNotOwnedError","httpStatus":null,"severity":"error","filePath":"redis/asyncio/lock.py","lineNumber":324,"sourceCode":"        the lock's existing ttl. If True, replace the lock's ttl with\n        `additional_time`.\n        \"\"\"\n        if self.local.token is None:\n            raise LockError(\"Cannot extend an unlocked lock\")\n        if self.timeout is None:\n            raise LockError(\"Cannot extend a lock with no timeout\")\n        return self.do_extend(additional_time, replace_ttl)\n\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            )","sourceCodeStart":306,"sourceCodeEnd":342,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/asyncio/lock.py#L306-L342","documentation":"do_extend runs the Lua extend script; if the key is gone/TTL-expired, the token differs, or the current PTTL is negative, the script returns 0 and LockNotOwnedError is raised. The lock expired before the extend request reached the server.","triggerScenarios":"Calling extend() after the lock TTL already expired; another client took over the key; the key was deleted between acquire and extend.","commonSituations":"Renewal interval longer than remaining TTL; GC/event-loop pauses pushing extend past expiry; network delay.","solutions":["Renew with margin before expiry (e.g. at one-third of the TTL).","Treat as expired (the work may be unsafe) and abort/log.","Use a longer base timeout so renewal has room.","Catch LockNotOwnedError and re-acquire fresh."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"from redis.exceptions import LockNotOwnedError\ntry:\n    await lock.extend(additional_time)\nexcept LockNotOwnedError:\n    # lock expired before extend: abort unsafe work\n    ...","preventionTips":["Renew at a fraction of the TTL so extend runs before expiry.","Make critical sections abort-safe in case the lock is lost.","Use a longer base timeout when renewal jitter is high."],"tags":["lock","ttl","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"}