redis/redis-py · warning · LockError
Unable to acquire lock within the time specified
Error message
Unable to acquire lock within the time specified
What it means
Raised as LockError by Lock.__aenter__ when acquire() returns False, i.e., the lock could not be obtained before blocking_timeout elapsed (or immediately if blocking=False). This is the context-manager equivalent of a failed acquire: 'async with lock:' translates a False return into this exception.
Source
Thrown at redis/asyncio/lock.py:173
self.local = threading.local() if self.thread_local else SimpleNamespace()
self.raise_on_release_error = raise_on_release_error
self.local.token = None
self.register_scripts()
def register_scripts(self):
cls = self.__class__
client = self.redis
if cls.lua_release is None:
cls.lua_release = client.register_script(cls.LUA_RELEASE_SCRIPT)
if cls.lua_extend is None:
cls.lua_extend = client.register_script(cls.LUA_EXTEND_SCRIPT)
if cls.lua_reacquire is None:
cls.lua_reacquire = client.register_script(cls.LUA_REACQUIRE_SCRIPT)
async def __aenter__(self):
if await self.acquire():
return self
raise LockError("Unable to acquire lock within the time specified")
async def __aexit__(self, exc_type, exc_value, traceback):
try:
await self.release()
except LockError:
if self.raise_on_release_error:
raise
logger.warning(
"Lock was unlocked or no longer owned when exiting context manager."
)
async def acquire(
self,
blocking: Optional[bool] = None,
blocking_timeout: Optional[Number] = None,
token: Optional[Union[str, bytes]] = None,
):
"""View on GitHub (pinned to da03cdc7e8)
Solutions
- Increase blocking_timeout (or the lock timeout) to tolerate contention.
- Ensure critical sections are short and always release the lock.
- Handle the failure path: acquire(blocking=False) and degrade instead of asserting the lock.
- Make sure the lock has a timeout so a crashed owner's lock auto-expires.
Example fix
// before
async with lock: # raises LockError if not acquired in time
...
// after
if await lock.acquire(blocking=False):
try:
...
finally:
await lock.release()
else:
# fall back / skip
... Defensive patterns
Strategy: try-catch
Validate before calling
async def acquire_or_fallback(lock):
if await lock.acquire(blocking=False):
return True
# non-blocking failure -> degrade instead of raising in async with
return False Type guard
def lock_has_blocking_timeout(lock) -> bool:
return lock.blocking and lock.blocking_timeout is not None Try / catch
from redis.exceptions import LockError
try:
async with lock:
...
except LockError as e:
if 'Unable to acquire' in str(e):
# degrade / skip the critical section
... Prevention
- Use acquire(blocking=False) when the lock is optional.
- Size blocking_timeout to expected contention.
- Keep critical sections short; always release.
- Set a lock timeout so crashed owners auto-expire.
When it happens
Trigger: Using 'async with lock:' where the lock is held by another owner for longer than the configured blocking_timeout, or blocking=False and the lock is currently held. acquire() returns False and __aenter__ raises at lock.py:173.
Common situations: High lock contention; a deadlock or long critical section; blocking_timeout too short; an owner that crashed without releasing (until the TTL expires); using the context manager where you wanted non-blocking semantics.
Related errors
- Cannot extend a lock with no timeout
- Cannot reacquire a lock with no timeout
- Watched variable changed.
- No connection available.
- Cannot release a lock that's not owned or is already unlocke
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/79b00ee8bf2de581.json.
Report an issue: GitHub.