{"record":{"id":"6892ed8ccf46c6b0","repo":"redis/redis-py","slug":"cannot-extend-a-lock-with-no-timeout-6892ed","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/lock.py","lineNumber":303,"sourceCode":"            )\n\n    def extend(\n        self, additional_time: Number, replace_ttl: bool = False\n    ) -> 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\", lock_name=self.name)\n        if self.timeout is None:\n            raise LockError(\"Cannot extend a lock with no timeout\", lock_name=self.name)\n        return self.do_extend(additional_time, replace_ttl)\n\n    def do_extend(self, additional_time: Number, replace_ttl: bool) -> Literal[True]:\n        additional_time = int(additional_time * 1000)\n        if not bool(\n            self.lua_extend(\n                keys=[self.name],\n                args=[self.local.token, additional_time, \"1\" if replace_ttl else \"0\"],\n                client=self.redis,\n            )\n        ):\n            raise LockNotOwnedError(\n                \"Cannot extend a lock that's no longer owned\",\n                lock_name=self.name,\n            )\n        return True\n\n    def reacquire(self) -> Literal[True]:","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/lock.py#L285-L321","documentation":"Raised by Lock.extend() when self.timeout is None, i.e. the Lock was constructed without a timeout (a permanent lock). Extending works by adjusting an existing TTL server-side via the LUA_EXTEND script, which has no TTL to operate on for a permanent lock. This is a client-side precondition check (LockError) raised at lock.py:303.","triggerScenarios":"Constructing Lock(redis, name, timeout=None) (or omitting timeout when the client default is None) and then calling extend(additional_time); acquiring a permanent lock and attempting to extend its TTL.","commonSituations":"Passing timeout=0 or timeout=None intending a long-lived lock but then needing to refresh it; copy-pasting extend/reacquire logic from a TTL-based lock into a permanent-lock workflow; assuming the client's default socket_timeout also sets the lock timeout.","solutions":["Construct the Lock with a positive numeric timeout (seconds), e.g. Lock(client, 'name', timeout=30).","If you need a permanent lock, do not call extend()/reacquire() — there is no TTL to refresh.","Switch to reacquire()/extend() only after creating the lock with a finite timeout."],"exampleFix":"# before\nlock = client.lock('mylock', timeout=None)\nlock.acquire()\nlock.extend(10)  # raises\n\n# after\nlock = client.lock('mylock', timeout=30)\nlock.acquire()\nlock.extend(10)  # ok","handlingStrategy":"validation","validationCode":"# Validate the lock has a TTL before extending\nif lock.timeout is None:\n    raise RuntimeError('cannot extend: lock has no timeout (permanent lock)')\nlock.extend(additional_time)","typeGuard":"from redis.lock import Lock\n\ndef is_ttl_lock(lock: Lock) -> bool:\n    \"\"\"True when the lock was created with a finite timeout.\"\"\"\n    return lock.timeout is not None and lock.timeout > 0","tryCatchPattern":"from redis.exceptions import LockError\n\ntry:\n    lock.extend(additional_time)\nexcept LockError as e:\n    if 'no timeout' in str(e):\n        raise RuntimeError('recreate the Lock with a finite timeout to use extend()')\n    raise","preventionTips":["Always pass a positive timeout when constructing locks you intend to refresh.","Do not call extend()/reacquire() on permanent (timeout=None) locks.","Centralize lock construction so timeout is set consistently."],"tags":["lock","distributed-lock","ttl","precondition"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}