redis/redis-py · error · LockError
Cannot reacquire a lock with no timeout
Error message
Cannot reacquire a lock with no timeout
What it means
Raised by Lock.reacquire() when self.timeout is None, i.e. the Lock was constructed as a permanent (no-TTL) lock. reacquire() resets the TTL to self.timeout (lock.py:335: int(self.timeout * 1000)), so a permanent lock has no TTL value to reset to. This is a client-side precondition check (LockError) at lock.py:328.
Source
Thrown at redis/lock.py:328
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]:
"""
Resets a TTL of an already acquired lock back to a timeout value.
"""
if self.local.token is None:
raise LockError("Cannot reacquire an unlocked lock", lock_name=self.name)
if self.timeout is None:
raise LockError(
"Cannot reacquire a lock with no timeout",
lock_name=self.name,
)
return self.do_reacquire()
def do_reacquire(self) -> Literal[True]:
timeout = int(self.timeout * 1000)
if not bool(
self.lua_reacquire(
keys=[self.name], args=[self.local.token, timeout], client=self.redis
)
):
raise LockNotOwnedError(
"Cannot reacquire a lock that's no longer owned",
lock_name=self.name,
)
return True
View on GitHub (pinned to 6a6b581b48)
Solutions
- Construct the Lock with a finite timeout (e.g. timeout=30) if you intend to call reacquire().
- Do not call reacquire() on permanent locks — there is no TTL to reset.
- If you need both long-lived holding and TTL refresh, use a large but finite timeout.
Example fix
# before
lock = client.lock('mylock', timeout=None)
lock.acquire()
lock.reacquire() # raises
# after
lock = client.lock('mylock', timeout=30)
lock.acquire()
lock.reacquire() # ok — resets TTL to 30s Defensive patterns
Strategy: validation
Validate before calling
# Validate the lock has a TTL before reacquiring
if lock.timeout is None:
raise RuntimeError('cannot reacquire: lock has no timeout')
lock.reacquire() Type guard
from redis.lock import Lock
def is_ttl_lock(lock: Lock) -> bool:
return lock.timeout is not None and lock.timeout > 0 Try / catch
from redis.exceptions import LockError
try:
lock.reacquire()
except LockError as e:
if 'no timeout' in str(e):
raise RuntimeError('recreate the Lock with a finite timeout to use reacquire()')
raise Prevention
- Construct locks you intend to refresh with a finite timeout.
- Never call reacquire() on permanent locks.
- Centralize lock construction to enforce a default timeout.
When it happens
Trigger: Constructing Lock(redis, name, timeout=None) and later calling reacquire(); using a permanent lock in a heartbeat/refresh loop designed for TTL-based locks.
Common situations: Reusing a TTL-refresh pattern against a permanent lock; passing timeout=0/None deliberately for indefinite holding then attempting to refresh; misconfiguring the lock timeout.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Cannot extend 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/1fa5fcff9a8cc480.
Report an issue: GitHub.