{"record":{"id":"20259ed6a803e484","repo":"redis/redis-py","slug":"cannot-extend-a-lock-with-no-timeout","errorCode":null,"errorMessage":"Cannot extend a lock with no timeout","messagePattern":"Cannot extend a lock with no timeout","errorType":"exception","errorClass":"LockError","httpStatus":null,"severity":"error","filePath":"redis/asyncio/lock.py","lineNumber":312,"sourceCode":"            raise LockNotOwnedError(\"Cannot release a lock that's no longer owned\")\n\n    def extend(\n        self, additional_time: Number, replace_ttl: bool = False\n    ) -> Awaitable[Literal[True]]:\n        \"\"\"\n        Adds more time to an already acquired lock.\n\n        ``additional_time`` can be specified as an integer or a float, both\n        representing the number of seconds to add.\n\n        ``replace_ttl`` if False (the default), add `additional_time` to\n        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        \"\"\"","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/asyncio/lock.py#L294-L330","documentation":"extend() runs a Lua script that uses PTTL/PEXPIRE, so the lock must have been created with a timeout (TTL). A Lock created with timeout=None (the default, manual-release-only) has no TTL to extend, so the client rejects extend() with LockError before contacting Redis.","triggerScenarios":"Creating Lock(r, name) with the default timeout=None and then calling lock.extend(additional_time).","commonSituations":"Forgot to set timeout; reused an indefinite lock in a renewal pattern; mismatch between lock semantics and renewal logic.","solutions":["Create the lock with a timeout (Lock(r, name, timeout=30)).","If you need an indefinite lock, do not call extend; manage release manually."],"exampleFix":"# before\nlock = Lock(r, 'resource')  # timeout=None default\nawait lock.extend(10)  # LockError\n# after\nlock = Lock(r, 'resource', timeout=30)\nawait lock.extend(10)","handlingStrategy":"validation","validationCode":"if lock.timeout is None:\n    raise ConfigError('Cannot extend a lock without a timeout; create Lock(..., timeout=N)')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always construct renewable locks with a timeout.","Reserve extend() for TTL-based locks; manage indefinite locks manually."],"tags":["lock","ttl","config","async"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}