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
do_release runs the Lua release script that checks the stored token; if the key is gone (TTL expired) or the token differs, it returns 0 and LockNotOwnedError is raised. This is server-side confirmation the lock is no longer yours, most commonly because its TTL expired while you were still in the critical section.
Solutions
- Ensure critical sections finish well within timeout, or extend() the lock proactively before it expires.
- Treat LockNotOwnedError as 'lock expired, the work may be unsafe' and abort/log.
- Use a longer timeout or a renewal task.
- Never manually delete lock keys.
Defensive patterns
Strategy: try-catch
Try / catch
from redis.exceptions import LockNotOwnedError
try:
await lock.release()
except LockNotOwnedError:
# TTL expired mid-section: work may be unsafe, abort/log
... Prevention
- Finish critical sections well within timeout, or extend() proactively.
- Treat LockNotOwnedError as an expired-lock signal and make the operation abort-safe.
- Never manually delete lock keys.
When it happens
Trigger: Calling release() after the lock's timeout/TTL expired; the key was deleted or overwritten by another client; token mismatch.
Common situations: Critical section longer than timeout; manual del of the lock key; another holder overwrote it after expiry.
Related errors
- Cannot extend a lock that's no longer owned
- Cannot reacquire a lock that's no longer owned
- Cannot extend an unlocked lock
- Cannot reacquire an unlocked lock
- Cannot release a lock that's not owned or is already…
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/2664c1c275c9c338.
Report an issue: GitHub.
Appendix: source
Thrown at redis/asyncio/lock.py:294
"Cannot release a lock that's not owned or is already unlocked.",
lock_name=self.name,
)
try:
await self.do_release(expected_token)
except LockNotOwnedError:
# Lock doesn't exist in Redis, safe to clear token
self.local.token = None
raise
# Only clear token after successful release
self.local.token = None
async def do_release(self, expected_token: bytes) -> None:
if not bool(
await self.lua_release(
keys=[self.name], args=[expected_token], client=self.redis
)
):
raise LockNotOwnedError("Cannot release a lock that's no longer owned")
def extend(
self, additional_time: Number, replace_ttl: bool = False
) -> Awaitable[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:
raise LockError("Cannot extend an unlocked lock")
if self.timeout is None:
raise LockError("Cannot extend a lock with no timeout")View on GitHub (pinned to 6a6b581b48)