redis/redis-py · error · LockNotOwnedError
Cannot extend a lock that's no longer owned
Error message
Cannot extend a lock that's no longer owned
What it means
Raised as LockNotOwnedError by Lock.do_extend() (lock.py:315) when the server-side LUA_EXTEND 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 over by another client). Unlike the two precondition errors above, this fires after a Redis round-trip and indicates ownership was lost at the server.
Source
Thrown at redis/lock.py:315
the lock's existing ttl. If True, replace the lock's ttl with
`additional_time`.
"""
if self.local.token is None:
raise LockError("Cannot extend an unlocked lock", lock_name=self.name)
if self.timeout is None:
raise LockError("Cannot extend a lock with no timeout", lock_name=self.name)
return self.do_extend(additional_time, replace_ttl)
def do_extend(self, additional_time: Number, replace_ttl: bool) -> Literal[True]:
additional_time = int(additional_time * 1000)
if not bool(
self.lua_extend(
keys=[self.name],
args=[self.local.token, additional_time, "1" if replace_ttl else "0"],
client=self.redis,
)
):
raise LockNotOwnedError(
"Cannot extend a lock that's no longer owned",
lock_name=self.name,
)
return True
def reacquire(self) -> 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", 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()
View on GitHub (pinned to 6a6b581b48)
Solutions
- Catch LockNotOwnedError specifically and re-acquire the lock before continuing critical work.
- Increase the base timeout or call extend() more frequently (well before the TTL expires).
- Check lock.owned() immediately before extend() and skip/handle gracefully if False.
- Review the work segment length so it completes well within the timeout window.
Example fix
# before
lock.extend(5)
# after
try:
lock.extend(5)
except LockNotOwnedError:
# lock expired — must re-acquire before doing more protected work
if lock.acquire(blocking=True, blocking_timeout=10):
do_critical_work()
else:
abort_work() Defensive patterns
Strategy: try-catch
Validate before calling
# Pre-check ownership server-side before extending
if not lock.owned():
# ownership already lost — re-acquire instead of extending
raise RuntimeError('lock not owned server-side; re-acquire')
lock.extend(additional_time) Type guard
from redis.lock import Lock
def still_owned(lock: Lock) -> bool:
"""True when the server still holds this client's token."""
return lock.owned() Try / catch
from redis.exceptions import LockNotOwnedError
try:
lock.extend(additional_time)
except LockNotOwnedError:
# TTL expired / taken over — must re-acquire before continuing protected work
if not lock.acquire(blocking=True, blocking_timeout=10):
raise RuntimeError('could not re-acquire expired lock') Prevention
- Call extend() well before the TTL expires (e.g. every timeout/3).
- Make processing segments shorter than the lock timeout.
- Always handle LockNotOwnedError on extend() in long-running jobs.
When it happens
Trigger: The lock's TTL elapsed before extend() was called (additional_time exceeded the remaining TTL); another process called release() or overwrote the key; a Redis FLUSHALL/EVICT removed the key; clock drift between client timing of extend and server-side pttl.
Common situations: Long-running jobs whose processing time exceeds the lock timeout and extend() is called too late; multiple workers contending for the same lock name; Redis under memory pressure evicting keys; mis-sizing additional_time relative to the base timeout.
Related errors
- Cannot reacquire 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/2ad574537f19c1ce.
Report an issue: GitHub.