{"id":"c5d47d88e0776d55","repo":"redis/redis-py","slug":"cannot-reacquire-a-lock-that-s-no-longer-owned","errorCode":null,"errorMessage":"Cannot reacquire a lock that's no longer owned","messagePattern":"Cannot reacquire a lock that's no longer owned","errorType":"exception","errorClass":"LockNotOwnedError","httpStatus":null,"severity":"error","filePath":"redis/asyncio/lock.py","lineNumber":344,"sourceCode":"\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":326,"sourceCodeEnd":346,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/lock.py#L326-L346","documentation":"Raised by Lock.do_reacquire() (the async distributed lock) when the server-side Lua reacquire script returns 0. That return value means the lock key no longer exists at the given name, or its value no longer matches this Lock's token, so this client no longer owns it. Reacquire resets an existing lock's TTL back to its configured timeout, so it is only valid while ownership is still held server-side. The lock is most commonly lost because the TTL elapsed before reacquire was called.","triggerScenarios":"Calling `await lock.reacquire()` after the lock's TTL has expired on the Redis server, after another client overwrote/deleted the key, or after the same Lock object already released it. Also triggered if `lock.local.token` is set (so the `Cannot reacquire an unlocked lock` guard at lock.py:332 is passed) but the server key is gone. The precondition check at lock.py:333 also requires `self.timeout is not None`.","commonSituations":"Setting a short `timeout` on the lock and calling reacquire too late; long-running work that outlasts the TTL; a Redis FLUSHDB/eviction removing the key; clock drift between client and server making the client believe the lock is still valid; sharing a Lock object across coroutines and one branch releases it while another reacquires.","solutions":["Catch `LockNotOwnedError` from `reacquire()` and treat it as 'lost lock' — re-acquire from scratch with `await lock.acquire()` before continuing critical work.","Increase the lock `timeout` (or call `extend` more frequently) so reacquire happens before the server TTL expires.","Audit for code paths that release the lock concurrently while another task calls reacquire on the same Lock instance.","Confirm no external process (admin tool, another service, Redis eviction) is deleting the lock key."],"exampleFix":"# before\nlock = client.lock('res', timeout=5)\nawait lock.acquire()\n# ... long work ...\nawait lock.reacquire()  # raises LockNotOwnedError after 5s\n\n# after\nfrom redis.exceptions import LockNotOwnedError\ntry:\n    await lock.reacquire()\nexcept LockNotOwnedError:\n    await lock.acquire()  # re-acquire ownership before continuing","handlingStrategy":"try-catch","validationCode":"import redis.asyncio as redis\n\nasync def owns_lock(client, lock) -> bool:\n    # Cheap pre-check: key exists and token matches before reacquire.\n    tok = await client.get(lock.name)\n    return tok is not None and tok == lock.local.token","typeGuard":"from redis.asyncio import Lock\n\ndef is_held_lock(obj) -> bool:\n    return isinstance(obj, Lock) and obj.local.token is not None and obj.timeout is not None","tryCatchPattern":"from redis.exceptions import LockNotOwnedError\n\ntry:\n    await lock.reacquire()\nexcept LockNotOwnedError:\n    # ownership lost (TTL elapsed / key gone) -> re-acquire\n    await lock.acquire()","preventionTips":["Set lock timeouts comfortably longer than the worst-case work duration and extend proactively via `extend`.","Never share a single Lock object across concurrently-executing tasks that may release/reacquire it.","Always treat reacquire/extend as fallible and wrap them in LockNotOwnedError handling."],"tags":["lock","distributed-lock","ttl","async"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}