{"record":{"id":"e47be64803fd70ba","repo":"redis/redis-py","slug":"cannot-reacquire-an-unlocked-lock-e47be6","errorCode":null,"errorMessage":"Cannot reacquire an unlocked lock","messagePattern":"Cannot reacquire an unlocked lock","errorType":"exception","errorClass":"LockError","httpStatus":null,"severity":"error","filePath":"redis/lock.py","lineNumber":326,"sourceCode":"        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]:\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            )","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/lock.py#L308-L344","documentation":"Raised by Lock.reacquire() when self.local.token is None, meaning the current thread holds no ownership token. reacquire() resets the lock's TTL back to its original timeout value, which requires the lock to currently be held by this thread. This is a client-side precondition check (LockError) at lock.py:326 fired before any Redis call.","triggerScenarios":"Calling lock.reacquire() before lock.acquire() succeeded; calling reacquire() after release(); calling reacquire() from a worker thread that did not perform the acquire when thread_local=True (default).","commonSituations":"Resetting a lock TTL in a background loop that started before acquire completed; reacquiring in cleanup paths that run after release; sharing a Lock object across threads without thread_local=False.","solutions":["Ensure acquire() returned True before calling reacquire().","Use the context manager (with lock:) to keep acquire/release balanced.","Guard with lock.owned() before reacquire().","Set thread_local=False when the lock must be reacquired by a different thread than the acquirer."],"exampleFix":"# before\nlock = client.lock('mylock', timeout=30)\nlock.reacquire()  # raises — not acquired\n\n# after\nlock = client.lock('mylock', timeout=30)\nlock.acquire()\ntry:\n    lock.reacquire()\nfinally:\n    lock.release()","handlingStrategy":"validation","validationCode":"# Validate ownership before reacquiring\nif lock.local.token is None or not lock.owned():\n    raise RuntimeError('cannot reacquire: lock not held by this thread')\nlock.reacquire()","typeGuard":"from redis.lock import Lock\n\ndef is_held(lock: Lock) -> bool:\n    return lock.local.token is not None and lock.owned()","tryCatchPattern":"from redis.exceptions import LockError\n\ntry:\n    lock.reacquire()\nexcept LockError as e:\n    if 'unlocked' in str(e):\n        if lock.acquire(blocking=True, blocking_timeout=5):\n            lock.reacquire()\n    else:\n        raise","preventionTips":["Balance acquire/release with the context manager.","Run reacquire() on the acquiring thread, or set thread_local=False.","Verify acquire() returned True before entering a reacquire heartbeat loop."],"tags":["lock","distributed-lock","precondition","thread-local"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}