redis/redis-py · error · LockError
Cannot reacquire an unlocked lock
Error message
Cannot reacquire an unlocked lock
What it means
Raised by Lock.reacquire() when self.local.token is None, meaning the current thread holds no ownership token. reacquire() resets the lock's TTL back to its original timeout value, which requires the lock to currently be held by this thread. This is a client-side precondition check (LockError) at lock.py:326 fired before any Redis call.
Source
Thrown at redis/lock.py:326
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]:
"""
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,
)View on GitHub (pinned to 6a6b581b48)
Solutions
- Ensure acquire() returned True before calling reacquire().
- Use the context manager (with lock:) to keep acquire/release balanced.
- Guard with lock.owned() before reacquire().
- Set thread_local=False when the lock must be reacquired by a different thread than the acquirer.
Example fix
# before
lock = client.lock('mylock', timeout=30)
lock.reacquire() # raises — not acquired
# after
lock = client.lock('mylock', timeout=30)
lock.acquire()
try:
lock.reacquire()
finally:
lock.release() Defensive patterns
Strategy: validation
Validate before calling
# Validate ownership before reacquiring
if lock.local.token is None or not lock.owned():
raise RuntimeError('cannot reacquire: lock not held by this thread')
lock.reacquire() Type guard
from redis.lock import Lock
def is_held(lock: Lock) -> bool:
return lock.local.token is not None and lock.owned() Try / catch
from redis.exceptions import LockError
try:
lock.reacquire()
except LockError as e:
if 'unlocked' in str(e):
if lock.acquire(blocking=True, blocking_timeout=5):
lock.reacquire()
else:
raise Prevention
- Balance acquire/release with the context manager.
- Run reacquire() on the acquiring thread, or set thread_local=False.
- Verify acquire() returned True before entering a reacquire heartbeat loop.
When it happens
Trigger: Calling lock.reacquire() before lock.acquire() succeeded; calling reacquire() after release(); calling reacquire() from a worker thread that did not perform the acquire when thread_local=True (default).
Common situations: Resetting a lock TTL in a background loop that started before acquire completed; reacquiring in cleanup paths that run after release; sharing a Lock object across threads without thread_local=False.
Related errors
- Cannot extend an unlocked lock
- Cannot extend a lock with no timeout
- Cannot reacquire a lock with no timeout
- Unable to acquire lock within the time specified
- Cannot release a lock that's not owned or is already unlocke
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/e47be64803fd70ba.
Report an issue: GitHub.