redis/redis-py · error · LockError
Cannot extend an unlocked lock
Error message
Cannot extend an unlocked lock
What it means
extend() first checks the local token; if it is None the lock was never acquired (or already released) on this instance, so the client refuses to run the Lua extend and raises LockError before touching Redis.
Solutions
- Acquire before extend; guard with 'if await lock.owned()'.
- Use thread_local=False for cross-task renewal so the token is shared.
- Do not extend after release.
Example fix
# before
await lock.extend(10) # before any acquire -> LockError
# after
if await lock.owned():
await lock.extend(10) Defensive patterns
Strategy: validation
Validate before calling
if await lock.owned():
await lock.extend(additional_time) Prevention
- Only extend after a successful acquire on the same task.
- Use thread_local=False when a separate task renews the lock.
When it happens
Trigger: Calling extend(...) before acquire(), after release(), or from a different thread/task than the acquirer when thread_local=True.
Common situations: A renewal task started before acquisition; extending after release; cross-task token invisibility with the default thread_local=True.
Related errors
- Cannot extend a lock that's no longer owned
- Cannot reacquire a lock that's no longer owned
- Cannot reacquire an unlocked lock
- Cannot release a lock that's no longer owned
- 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/bf1ad8843f9471e2.
Report an issue: GitHub.
Appendix: source
Thrown at redis/asyncio/lock.py:310
)
):
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")
return self.do_extend(additional_time, replace_ttl)
async def do_extend(self, additional_time, replace_ttl) -> Literal[True]:
additional_time = int(additional_time * 1000)
if not bool(
await self.lua_extend(
keys=[self.name],
args=[self.local.token, additional_time, replace_ttl and "1" or "0"],
client=self.redis,
)
):
raise LockNotOwnedError("Cannot extend a lock that's no longer owned")
return True
def reacquire(self) -> Awaitable[Literal[True]]:
"""View on GitHub (pinned to 6a6b581b48)