redis/redis-py · warning · LockError
Cannot extend a lock with no timeout
Error message
Cannot extend a lock with no timeout
What it means
Raised as LockError by Lock.extend() when self.timeout is None, i.e., the Lock was constructed without a timeout. extend() needs the original timeout semantics to compute the new TTL; a lock with no timeout (manual-release-only) has no TTL to extend.
Source
Thrown at redis/asyncio/lock.py:312
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]]:
"""
Resets a TTL of an already acquired lock back to a timeout value.
"""View on GitHub (pinned to da03cdc7e8)
Solutions
- Construct the Lock with a finite timeout (e.g., timeout=30) so extend() has a TTL to work with.
- If you want to add a TTL after the fact, reacquire the lock with a timeout instead of extend().
- Use EXPIRE directly via redis.expire(lock.name, ttl) only if you understand the ownership implications.
Example fix
// before lock = Lock(redis, 'k') # timeout=None default await lock.acquire() lock.extend(30) # LockError // after lock = Lock(redis, 'k', timeout=30) await lock.acquire() lock.extend(30)
Defensive patterns
Strategy: validation
Validate before calling
def can_extend(lock) -> bool:
return lock.timeout is not None
if not can_extend(lock):
lock = Lock(redis, name, timeout=30) # recreate with a TTL
await lock.acquire()
lock.extend(30) Type guard
def lock_has_timeout(lock) -> bool:
return lock.timeout is not None Try / catch
from redis.exceptions import LockError
try:
lock.extend(10)
except LockError as e:
if 'no timeout' in str(e):
# recreate the lock with a timeout
... Prevention
- Construct Lock with a finite timeout if you intend to extend.
- Don't use extend() on manual-release (timeout=None) locks.
- For adding a TTL after the fact, reacquire with a timeout.
When it happens
Trigger: Constructing Lock(redis, name, timeout=None) (the default) and then calling lock.extend(...). The guard at lock.py:311-312 fires before any Redis call.
Common situations: Using a perpetual lock and later deciding to add a TTL via extend; copying config from a manual-release lock into an extend-based keepalive flow.
Related errors
- Unable to acquire lock within the time specified
- Cannot extend an unlocked lock
- Cannot extend a lock that's no longer owned
- Cannot reacquire a lock with no timeout
- No connection available.
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/20259ed6a803e484.json.
Report an issue: GitHub.