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

  1. Construct the Lock with a finite timeout (e.g. timeout=30) if you intend to call reacquire().
  2. Do not call reacquire() on permanent locks — there is no TTL to reset.
  3. 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

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

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/1fa5fcff9a8cc480. Report an issue: GitHub.