redis/redis-py · error · LockError
Cannot release a lock that's not owned or is already…
Error message
Cannot release a lock that's not owned or is already unlocked.
What it means
release() reads the local token; if it is None the lock was never acquired on this instance (or was already released in this task), so the client refuses to run the Lua release and raises LockError. With thread_local=True (default) the token is stored per-thread, so releasing from a different thread/task than the one that acquired looks like 'not owned'.
Solutions
- Only release after a successful acquire; guard with 'if await lock.owned()'.
- For cross-thread/task handoff, construct the Lock with thread_local=False.
- In context-manager usage set raise_on_release_error=False to suppress release errors on exit.
- Avoid calling release() twice.
Example fix
# before lock = Lock(r, 'resource') # thread_local=True default # acquired in task A, released in task B -> LockError # after lock = Lock(r, 'resource', thread_local=False) # token visible across tasks
Defensive patterns
Strategy: try-catch
Validate before calling
if await lock.owned():
await lock.release() Try / catch
from redis.exceptions import LockError
try:
await lock.release()
except LockError:
# never owned on this task: safe to ignore or log
... Prevention
- Use thread_local=False for locks handed off across threads/tasks.
- Guard release/extend/reacquire with 'await lock.owned()'.
- Set raise_on_release_error=False in context-manager usage where appropriate.
When it happens
Trigger: Calling release() before acquire(); calling release() twice; releasing from a different thread/task than acquired (thread_local=True); context-manager exit after a failed/contended acquisition.
Common situations: Cross-thread or cross-task lock handoff without thread_local=False; double release in finally blocks; reusing a Lock object and releasing before acquiring.
Related errors
- Cannot extend a lock that's no longer owned
- Cannot extend an unlocked lock
- Cannot reacquire a lock that's no longer owned
- Cannot reacquire an unlocked lock
- 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/7e9305955ba91a10.
Report an issue: GitHub.
Appendix: source
Thrown at redis/asyncio/lock.py:275
if stored_token and not isinstance(stored_token, bytes):
try:
encoder = self.redis.connection_pool.get_encoder()
except AttributeError:
# Cluster
encoder = self.redis.get_encoder()
stored_token = encoder.encode(stored_token)
return self.local.token is not None and stored_token == self.local.token
async def release(self) -> None:
"""Releases the already acquired lock.
The token is only cleared after the Redis release operation completes
successfully. This ensures that if the release is cancelled mid-operation,
the lock state remains consistent and can be retried.
"""
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,
)
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
)
):View on GitHub (pinned to 6a6b581b48)