redis/redis-py · error · LockError
Cannot extend a lock with no timeout
Error message
Cannot extend a lock with no timeout
What it means
Raised by Lock.extend() when self.timeout is None, i.e. the Lock was constructed without a timeout (a permanent lock). Extending works by adjusting an existing TTL server-side via the LUA_EXTEND script, which has no TTL to operate on for a permanent lock. This is a client-side precondition check (LockError) raised at lock.py:303.
Source
Thrown at redis/lock.py:303
)
def extend(
self, additional_time: Number, replace_ttl: bool = False
) -> 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", lock_name=self.name)
if self.timeout is None:
raise LockError("Cannot extend a lock with no timeout", lock_name=self.name)
return self.do_extend(additional_time, replace_ttl)
def do_extend(self, additional_time: Number, replace_ttl: bool) -> Literal[True]:
additional_time = int(additional_time * 1000)
if not bool(
self.lua_extend(
keys=[self.name],
args=[self.local.token, additional_time, "1" if replace_ttl else "0"],
client=self.redis,
)
):
raise LockNotOwnedError(
"Cannot extend a lock that's no longer owned",
lock_name=self.name,
)
return True
def reacquire(self) -> Literal[True]:View on GitHub (pinned to 6a6b581b48)
Solutions
- Construct the Lock with a positive numeric timeout (seconds), e.g. Lock(client, 'name', timeout=30).
- If you need a permanent lock, do not call extend()/reacquire() — there is no TTL to refresh.
- Switch to reacquire()/extend() only after creating the lock with a finite timeout.
Example fix
# before
lock = client.lock('mylock', timeout=None)
lock.acquire()
lock.extend(10) # raises
# after
lock = client.lock('mylock', timeout=30)
lock.acquire()
lock.extend(10) # ok Defensive patterns
Strategy: validation
Validate before calling
# Validate the lock has a TTL before extending
if lock.timeout is None:
raise RuntimeError('cannot extend: lock has no timeout (permanent lock)')
lock.extend(additional_time) Type guard
from redis.lock import Lock
def is_ttl_lock(lock: Lock) -> bool:
"""True when the lock was created with a finite timeout."""
return lock.timeout is not None and lock.timeout > 0 Try / catch
from redis.exceptions import LockError
try:
lock.extend(additional_time)
except LockError as e:
if 'no timeout' in str(e):
raise RuntimeError('recreate the Lock with a finite timeout to use extend()')
raise Prevention
- Always pass a positive timeout when constructing locks you intend to refresh.
- Do not call extend()/reacquire() on permanent (timeout=None) locks.
- Centralize lock construction so timeout is set consistently.
When it happens
Trigger: Constructing Lock(redis, name, timeout=None) (or omitting timeout when the client default is None) and then calling extend(additional_time); acquiring a permanent lock and attempting to extend its TTL.
Common situations: Passing timeout=0 or timeout=None intending a long-lived lock but then needing to refresh it; copy-pasting extend/reacquire logic from a TTL-based lock into a permanent-lock workflow; assuming the client's default socket_timeout also sets the lock timeout.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Cannot reacquire a lock with no timeout
- Cannot release a lock that's no longer owned
- Cannot extend a lock that's no longer owned
- Cannot reacquire a lock that's no longer owned
- Cannot extend an unlocked lock
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/6892ed8ccf46c6b0.
Report an issue: GitHub.