{"record":{"id":"1fa5fcff9a8cc480","repo":"redis/redis-py","slug":"cannot-reacquire-a-lock-with-no-timeout-1fa5fc","errorCode":null,"errorMessage":"Cannot reacquire a lock with no timeout","messagePattern":"Cannot reacquire a lock with no timeout","errorType":"exception","errorClass":"LockError","httpStatus":null,"severity":"error","filePath":"redis/lock.py","lineNumber":328,"sourceCode":"                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]:\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\", lock_name=self.name)\n        if self.timeout is None:\n            raise LockError(\n                \"Cannot reacquire a lock with no timeout\",\n                lock_name=self.name,\n            )\n        return self.do_reacquire()\n\n    def do_reacquire(self) -> Literal[True]:\n        timeout = int(self.timeout * 1000)\n        if not bool(\n            self.lua_reacquire(\n                keys=[self.name], args=[self.local.token, timeout], client=self.redis\n            )\n        ):\n            raise LockNotOwnedError(\n                \"Cannot reacquire a lock that's no longer owned\",\n                lock_name=self.name,\n            )\n        return True\n","sourceCodeStart":310,"sourceCodeEnd":346,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/lock.py#L310-L346","documentation":"Raised by Lock.reacquire() when self.timeout is None, i.e. the Lock was constructed as a permanent (no-TTL) lock. reacquire() resets the TTL to self.timeout (lock.py:335: int(self.timeout * 1000)), so a permanent lock has no TTL value to reset to. This is a client-side precondition check (LockError) at lock.py:328.","triggerScenarios":"Constructing Lock(redis, name, timeout=None) and later calling reacquire(); using a permanent lock in a heartbeat/refresh loop designed for TTL-based locks.","commonSituations":"Reusing a TTL-refresh pattern against a permanent lock; passing timeout=0/None deliberately for indefinite holding then attempting to refresh; misconfiguring the lock timeout.","solutions":["Construct the Lock with a finite timeout (e.g. timeout=30) if you intend to call reacquire().","Do not call reacquire() on permanent locks — there is no TTL to reset.","If you need both long-lived holding and TTL refresh, use a large but finite timeout."],"exampleFix":"# before\nlock = client.lock('mylock', timeout=None)\nlock.acquire()\nlock.reacquire()  # raises\n\n# after\nlock = client.lock('mylock', timeout=30)\nlock.acquire()\nlock.reacquire()  # ok — resets TTL to 30s","handlingStrategy":"validation","validationCode":"# Validate the lock has a TTL before reacquiring\nif lock.timeout is None:\n    raise RuntimeError('cannot reacquire: lock has no timeout')\nlock.reacquire()","typeGuard":"from redis.lock import Lock\n\ndef is_ttl_lock(lock: Lock) -> bool:\n    return lock.timeout is not None and lock.timeout > 0","tryCatchPattern":"from redis.exceptions import LockError\n\ntry:\n    lock.reacquire()\nexcept LockError as e:\n    if 'no timeout' in str(e):\n        raise RuntimeError('recreate the Lock with a finite timeout to use reacquire()')\n    raise","preventionTips":["Construct locks you intend to refresh with a finite timeout.","Never call reacquire() on permanent locks.","Centralize lock construction to enforce a default timeout."],"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"}