redis/redis-py · error · LockNotOwnedError

Cannot reacquire a lock that's no longer owned

Error message

Cannot reacquire a lock that's no longer owned

What it means

Raised as LockNotOwnedError by Lock.do_reacquire() (lock.py:341) when the server-side LUA_REACQUIRE script returns 0. The script returns 0 when GET of the lock key does not match this client's token — the lock expired, was released, or was taken by another owner. This fires after a Redis round-trip and means ownership was lost server-side.

Source

Thrown at redis/lock.py:341

        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. Catch LockNotOwnedError and re-acquire the lock before continuing protected work.
  2. Make the heartbeat/reacquire interval shorter than the lock timeout (e.g. reacquire every timeout/3).
  3. Check lock.owned() before reacquire() and re-acquire if False.
  4. Tune the timeout upward relative to worst-case heartbeat latency.

Example fix

# before
while working:
    lock.reacquire()
    do_chunk()

# after
while working:
    try:
        lock.reacquire()
    except LockNotOwnedError:
        if not lock.acquire(blocking=True, blocking_timeout=10):
            break  # someone else owns it; stop
    do_chunk()
Defensive patterns

Strategy: try-catch

Validate before calling

# Pre-check ownership server-side before reacquiring
if not lock.owned():
    raise RuntimeError('lock not owned server-side; re-acquire')
lock.reacquire()

Type guard

from redis.lock import Lock

def still_owned(lock: Lock) -> bool:
    return lock.owned()

Try / catch

from redis.exceptions import LockNotOwnedError

try:
    lock.reacquire()
except LockNotOwnedError:
    if not lock.acquire(blocking=True, blocking_timeout=10):
        raise RuntimeError('could not re-acquire expired lock')

Prevention

When it happens

Trigger: Calling reacquire() after the lock's TTL already elapsed; another client acquired the same lock name after expiry; the key was evicted/flushed; server clock skew causing premature expiry.

Common situations: Heartbeat loops that wake up after the TTL window has closed; contention where a second worker grabbed the lock once it expired; Redis memory eviction; long GC pauses delaying the reacquire past TTL.

Related errors


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