redis/redis-py · warning · LockError

Cannot reacquire an unlocked lock

Error message

Cannot reacquire an unlocked lock

What it means

Raised as LockError by Lock.reacquire() when self.local.token is None: reacquire() (reset TTL to the original timeout) requires the lock to currently be owned by this instance. Without a token there is nothing to reacquire.

Source

Thrown at redis/asyncio/lock.py:332

    async def do_extend(self, additional_time, replace_ttl) -> Literal[True]:
        additional_time = int(additional_time * 1000)
        if not bool(
            await self.lua_extend(
                keys=[self.name],
                args=[self.local.token, additional_time, replace_ttl and "1" or "0"],
                client=self.redis,
            )
        ):
            raise LockNotOwnedError("Cannot extend a lock that's no longer owned")
        return True

    def reacquire(self) -> Awaitable[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")
        if self.timeout is None:
            raise LockError("Cannot reacquire a lock with no timeout")
        return self.do_reacquire()

    async def do_reacquire(self) -> Literal[True]:
        timeout = int(self.timeout * 1000)
        if not bool(
            await 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")
        return True

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Call reacquire() only after a successful acquire() and before release(), in the same task (or use thread_local=False).
  2. Stop keepalive loops when the token is cleared.
  3. Guard with owned() if the ownership state is uncertain.

Example fix

// before
lock = Lock(redis, 'k', timeout=30)
lock.reacquire()  # before acquire -> LockError
// after
await lock.acquire()
lock.reacquire()
Defensive patterns

Strategy: validation

Validate before calling

async def reacquire_if_owned(lock):
    if lock.local.token is None:
        return False
    if lock.timeout is None:
        return False
    return await lock.reacquire()

Type guard

def lock_holds_token(lock) -> bool:
    return getattr(getattr(lock, 'local', None), 'token', None) is not None

Try / catch

from redis.exceptions import LockError
try:
    lock.reacquire()
except LockError as e:
    if 'unlocked' in str(e):
        # reacquire after a fresh acquire() instead
        ...

Prevention

When it happens

Trigger: Calling lock.reacquire() before acquire() or after release(). Fires at lock.py:331-332.

Common situations: A keepalive loop calling reacquire() after the lock was released; reacquiring from a different task under thread_local=True; logic error calling reacquire without a prior successful acquire.

Related errors


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