redis/redis-py · error · LockNotOwnedError
Cannot release a lock that's no longer owned
Error message
Cannot release a lock that's no longer owned
What it means
Lock.do_release (redis/lock.py:282) raises LockNotOwnedError (a LockError subclass) when the server-side Lua release script returns falsy — the token stored at the lock key did not match the expected token. That means the lock was never held, already expired (timeout elapsed), or was stolen/re-acquired by another holder between acquire and release. Unlike error 438 (a client-side pre-check), this is the server confirming you no longer own the key.
Source
Thrown at redis/lock.py:282
def release(self) -> None:
"""
Releases the already acquired lock
"""
expected_token = self.local.token
if expected_token is None:
raise LockError(
"Cannot release a lock that's not owned or is already unlocked.",
lock_name=self.name,
)
self.local.token = None
self.do_release(expected_token)
def do_release(self, expected_token: str) -> None:
if not bool(
self.lua_release(keys=[self.name], args=[expected_token], client=self.redis)
):
raise LockNotOwnedError(
"Cannot release a lock that's no longer owned",
lock_name=self.name,
)
def extend(
self, additional_time: Number, replace_ttl: bool = False
) -> Literal[True]:
"""
Adds more time to an already acquired lock.
``additional_time`` can be specified as an integer or a float, both
representing the number of seconds to add.
``replace_ttl`` if False (the default), add `additional_time` to
the lock's existing ttl. If True, replace the lock's ttl with
`additional_time`.
"""
if self.local.token is None:View on GitHub (pinned to 6a6b581b48)
Solutions
- Set a lock timeout comfortably larger than your worst-case critical section, or call lock.extend() periodically for long work.
- Catch LockNotOwnedError on release and treat it as 'I may not have been the sole owner' — re-check/recover the protected resource.
- Use raise_on_release_error=True (default False in __exit__) only if losing ownership must be fatal; otherwise log it.
- Avoid manual del on the lock key; always release through the Lock API.
Example fix
// before
with Lock(r, 'task', timeout=5):
long_running_task() # may exceed 5s, lock expires
// after
from redis.exceptions import LockNotOwnedError
with Lock(r, 'task', timeout=60) as lock:
while working:
do_chunk(); lock.extend(60)
# and on release:
try:
lock.release()
except LockNotOwnedError:
log.warning('lock expired before release; verify protected state') Defensive patterns
Strategy: try-catch
Validate before calling
def still_owned(lock) -> bool:
return lock.owned() Type guard
def lock_is_owned(lock) -> bool:
return lock.owned() Try / catch
from redis.exceptions import LockNotOwnedError
try:
lock.release()
except LockNotOwnedError:
log.warning('lock expired before release; re-verify protected resource') Prevention
- Set the lock timeout above worst-case work duration, or call lock.extend() periodically for long sections.
- Treat a release-time LockNotOwnedError as a signal to re-check the protected resource.
- Never manually DEL the lock key; always release through the Lock API.
- Keep raise_on_release_error=False (default) unless losing ownership must be fatal.
When it happens
Trigger: release() after the lock TTL expired (your critical section took longer than the lock timeout); releasing a lock whose key was overwritten; the token lost because of a failover where the key lived on a node that went down; releasing after manually deleting the key.
Common situations: Long-running work exceeding the lock timeout so the lock silently expired and another worker took it; no extend() calls for long sections; a failover losing the lock key; mixing manual DEL with the Lock abstraction.
Related errors
- Cannot release a lock that's not owned or is already unlocke
- Cannot release a lock that's no longer owned
- Cannot extend an unlocked lock
- Cannot extend a lock that's no longer owned
- Cannot reacquire an unlocked lock
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/8fa21cfed0055188.
Report an issue: GitHub.