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 as LockNotOwnedError by Lock.do_reacquire() (lock.py:341) when the server-side LUA_REACQUIRE script returns 0. The script returns 0 when GET of the lock key does not match this client's token — the lock expired, was released, or was taken by another owner. This fires after a Redis round-trip and means ownership was lost server-side.
Source
Thrown at redis/lock.py:341
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", lock_name=self.name)
if self.timeout is None:
raise LockError(
"Cannot reacquire a lock with no timeout",
lock_name=self.name,
)
return self.do_reacquire()
def do_reacquire(self) -> Literal[True]:
timeout = int(self.timeout * 1000)
if not bool(
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",
lock_name=self.name,
)
return True
View on GitHub (pinned to 6a6b581b48)
Solutions
- Catch LockNotOwnedError and re-acquire the lock before continuing protected work.
- Make the heartbeat/reacquire interval shorter than the lock timeout (e.g. reacquire every timeout/3).
- Check lock.owned() before reacquire() and re-acquire if False.
- Tune the timeout upward relative to worst-case heartbeat latency.
Example fix
# before
while working:
lock.reacquire()
do_chunk()
# after
while working:
try:
lock.reacquire()
except LockNotOwnedError:
if not lock.acquire(blocking=True, blocking_timeout=10):
break # someone else owns it; stop
do_chunk() Defensive patterns
Strategy: try-catch
Validate before calling
# Pre-check ownership server-side before reacquiring
if not lock.owned():
raise RuntimeError('lock not owned server-side; re-acquire')
lock.reacquire() Type guard
from redis.lock import Lock
def still_owned(lock: Lock) -> bool:
return lock.owned() Try / catch
from redis.exceptions import LockNotOwnedError
try:
lock.reacquire()
except LockNotOwnedError:
if not lock.acquire(blocking=True, blocking_timeout=10):
raise RuntimeError('could not re-acquire expired lock') Prevention
- Run heartbeat/reacquire at an interval shorter than the timeout (e.g. timeout/3).
- Account for GC pauses and clock skew when sizing the interval.
- Always handle LockNotOwnedError in heartbeat loops.
When it happens
Trigger: Calling reacquire() after the lock's TTL already elapsed; another client acquired the same lock name after expiry; the key was evicted/flushed; server clock skew causing premature expiry.
Common situations: Heartbeat loops that wake up after the TTL window has closed; contention where a second worker grabbed the lock once it expired; Redis memory eviction; long GC pauses delaying the reacquire past TTL.
Related errors
- Cannot extend a lock that's no longer owned
- Cannot release 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
- Cannot release a lock that's no longer owned
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/cb27b714ad81b7c8.
Report an issue: GitHub.