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 redis.exceptions.LockError by Lock.__enter__ (redis/lock.py:170). When using the lock as a context manager, __enter__ calls acquire(); if acquire() returns False (it could not obtain the lock within blocking_timeout, or blocking was False and the lock was held), __enter__ raises so the `with` block never executes. LockError subclasses ValueError.

Source

Thrown at redis/lock.py:170

        self.raise_on_release_error = raise_on_release_error
        self.local = threading.local() if self.thread_local else SimpleNamespace()
        self.local.token = None
        self.register_scripts()

    def register_scripts(self) -> None:
        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)

    def __enter__(self) -> "Lock":
        if self.acquire():
            return self
        raise LockError(
            "Unable to acquire lock within the time specified",
            lock_name=self.name,
        )

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_value: Optional[BaseException],
        traceback: Optional[TracebackType],
    ) -> None:
        try:
            self.release()
        except LockError:
            if self.raise_on_release_error:
                raise
            logger.warning(
                "Lock was unlocked or no longer owned when exiting context manager."
            )

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Catch redis.exceptions.LockError around the `with` statement and degrade gracefully (skip, queue, or retry).
  2. Increase blocking_timeout (or set a lock timeout so stale locks expire sooner).
  3. Ensure lock holders always release in a finally block, and set a sensible lock timeout so crashed holders' locks expire.
  4. Use a unique lock name per resource to avoid false contention.
  5. If non-blocking semantics are wanted, handle the False return / LockError as 'busy, try later'.

Example fix

// before
with client.lock('job-1', blocking_timeout=5):
    do_work()  # LockError if not acquired in 5s

// after
from redis.exceptions import LockError
try:
    with client.lock('job-1', blocking_timeout=30):
        do_work()
except LockError:
    log.info('job-1 busy, skipping this run')
Defensive patterns

Strategy: try-catch

Validate before calling

from redis.exceptions import LockError
# probe non-blocking first to avoid the LockError from __enter__
lock = client.lock('job-1')
if not lock.acquire(blocking=False):
    log.info('job-1 busy')
else:
    try:
        do_work()
    finally:
        lock.release()

Try / catch

from redis.exceptions import LockError
try:
    with client.lock('job-1', blocking_timeout=30):
        do_work()
except LockError:
    log.info('job-1 busy, skipping')

Prevention

When it happens

Trigger: Using `with client.lock(name, blocking_timeout=N):` (or blocking=False) when the lock key is already held by another holder for longer than blocking_timeout, or when blocking=False and the lock is contended.

Common situations: A previous holder crashed without releasing (TTL still running); high contention on a hot lock name; blocking_timeout too short for the workload; deadlock between competing workers.

Related errors


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