redis/redis-py · error · LockNotOwnedError
Cannot reacquire a lock that's no longer owned
Error message
Cannot reacquire a lock that's no longer owned
What it means
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.
Source
Thrown at redis/asyncio/lock.py:344
def reacquire(self) -> Awaitable[Literal[True]]:
"""
Resets a TTL of an already acquired lock back to a timeout value.
"""
if self.local.token is None:
raise LockError("Cannot reacquire an unlocked lock")
if self.timeout is None:
raise LockError("Cannot reacquire a lock with no timeout")
return self.do_reacquire()
async def do_reacquire(self) -> Literal[True]:
timeout = int(self.timeout * 1000)
if not bool(
await self.lua_reacquire(
keys=[self.name], args=[self.local.token, timeout], client=self.redis
)
):
raise LockNotOwnedError("Cannot reacquire a lock that's no longer owned")
return True
View on GitHub (pinned to da03cdc7e8)
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.
Example fix
# before
lock = client.lock('res', timeout=5)
await lock.acquire()
# ... long work ...
await lock.reacquire() # raises LockNotOwnedError after 5s
# after
from redis.exceptions import LockNotOwnedError
try:
await lock.reacquire()
except LockNotOwnedError:
await lock.acquire() # re-acquire ownership before continuing Defensive patterns
Strategy: try-catch
Validate before calling
import redis.asyncio as redis
async def owns_lock(client, lock) -> bool:
# Cheap pre-check: key exists and token matches before reacquire.
tok = await client.get(lock.name)
return tok is not None and tok == lock.local.token Type guard
from redis.asyncio import Lock
def is_held_lock(obj) -> bool:
return isinstance(obj, Lock) and obj.local.token is not None and obj.timeout is not None Try / catch
from redis.exceptions import LockNotOwnedError
try:
await lock.reacquire()
except LockNotOwnedError:
# ownership lost (TTL elapsed / key gone) -> re-acquire
await lock.acquire() Prevention
- 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.
When it happens
Trigger: 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`.
Common situations: 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.
Related errors
- Cannot release a lock that's no longer owned
- Cannot extend a lock that's no longer owned
- Unable to acquire lock within the time specified
- Cannot release a lock that's not owned or is already unlocke
- Connection closed by server.
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/c5d47d88e0776d55.json.
Report an issue: GitHub.