redis/redis-py · warning · LockError
Cannot extend an unlocked lock
Error message
Cannot extend an unlocked lock
What it means
Raised as LockError by Lock.extend() when self.local.token is None: there is no ownership token to extend. extend() only makes sense on a lock that has been acquired; without a token the Lua extend script cannot run.
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 da03cdc7e8)
Solutions
- Only call extend() between a successful acquire() and release(), within the same task (or use thread_local=False).
- Guard the watchdog loop to stop extending once the token is cleared/released.
- Use owned() to check ownership before extending if unsure.
Example fix
// before lock = Lock(redis, 'k', timeout=10) lock.extend(10) # before acquire -> LockError // after await lock.acquire() lock.extend(10) ...
Defensive patterns
Strategy: validation
Validate before calling
def can_extend(lock) -> bool:
return lock.local.token is not None and lock.timeout is not None
if can_extend(lock):
lock.extend(10) Type guard
def lock_holds_token(lock) -> bool:
return getattr(getattr(lock, 'local', None), 'token', None) is not None Try / catch
from redis.exceptions import LockError
try:
lock.extend(10)
except LockError as e:
if 'unlocked' in str(e):
# not acquired (or already released); skip extend
... Prevention
- Only extend() between acquire() and release() in the same task.
- Stop watchdog loops when the token is cleared.
- Use owned() before extending if state is uncertain.
When it happens
Trigger: Calling lock.extend(...) before acquire() succeeded, or after release() has already cleared the token. Fires at lock.py:309-310.
Common situations: Extending in a watchdog/keepalive loop that starts before acquisition completes; extending after an early release; extending from a different task under thread_local=True.
Related errors
- Cannot extend a lock that's no longer owned
- Cannot release a lock that's not owned or is already unlocke
- Cannot release a lock that's no longer owned
- Cannot extend a lock with no timeout
- Cannot reacquire an unlocked lock
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/bf1ad8843f9471e2.json.
Report an issue: GitHub.