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

  1. Increase blocking_timeout (or the lock timeout) to tolerate contention.
  2. Ensure critical sections are short and always release the lock.
  3. Handle the failure path: acquire(blocking=False) and degrade instead of asserting the lock.
  4. 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

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


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/79b00ee8bf2de581.json. Report an issue: GitHub.