{"id":"b0ade59d493f5a8e","repo":"redis/redis-py","slug":"cannot-reacquire-a-lock-with-no-timeout","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":"warning","filePath":"redis/asyncio/lock.py","lineNumber":334,"sourceCode":"        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":316,"sourceCodeEnd":346,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/lock.py#L316-L346","documentation":"Raised as LockError by Lock.reacquire() when self.timeout is None. reacquire() resets the TTL back to the lock's configured timeout; if the Lock has no timeout (manual-release-only), there is no TTL value to reset to, so the operation is undefined and rejected.","triggerScenarios":"Constructing Lock(redis, name) with the default timeout=None and calling lock.reacquire(). Fires at lock.py:333-334.","commonSituations":"Using a perpetual lock and later wanting to refresh its TTL via reacquire; copying a no-timeout lock into a keepalive flow.","solutions":["Construct the Lock with a finite timeout so reacquire() has a TTL to reset to.","If you need to (re)set a TTL on a no-timeout lock, acquire it with a timeout instead.","Avoid reacquire() on manual-release locks by design."],"exampleFix":"// before\nlock = Lock(redis, 'k')  # timeout=None default\nawait lock.acquire()\nlock.reacquire()  # LockError\n// after\nlock = Lock(redis, 'k', timeout=30)\nawait lock.acquire()\nlock.reacquire()","handlingStrategy":"validation","validationCode":"def can_reacquire(lock) -> bool:\n    return lock.timeout is not None\n\nif not can_reacquire(lock):\n    lock = Lock(redis, name, timeout=30)  # recreate with a TTL\n\nawait lock.acquire()\nawait lock.reacquire()","typeGuard":"def lock_has_timeout(lock) -> bool:\n    return lock.timeout is not None","tryCatchPattern":"from redis.exceptions import LockError\ntry:\n    lock.reacquire()\nexcept LockError as e:\n    if 'no timeout' in str(e):\n        # recreate the lock with a timeout\n        ...","preventionTips":["Construct Lock with a finite timeout if you intend to reacquire.","Avoid reacquire() on manual-release (timeout=None) locks.","Reacquire with a timeout if you need to add a TTL later."],"tags":["lock","timeout","reacquire","asyncio"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}