redis/redis-py · error · LockError

Cannot extend a lock with no timeout

Error message

Cannot extend a lock with no timeout

What it means

extend() runs a Lua script that uses PTTL/PEXPIRE, so the lock must have been created with a timeout (TTL). A Lock created with timeout=None (the default, manual-release-only) has no TTL to extend, so the client rejects extend() with LockError before contacting Redis.

Solutions

  1. Create the lock with a timeout (Lock(r, name, timeout=30)).
  2. If you need an indefinite lock, do not call extend; manage release manually.

Example fix

# before
lock = Lock(r, 'resource')  # timeout=None default
await lock.extend(10)  # LockError
# after
lock = Lock(r, 'resource', timeout=30)
await lock.extend(10)
Defensive patterns

Strategy: validation

Validate before calling

if lock.timeout is None:
    raise ConfigError('Cannot extend a lock without a timeout; create Lock(..., timeout=N)')

Prevention

When it happens

Trigger: Creating Lock(r, name) with the default timeout=None and then calling lock.extend(additional_time).

Common situations: Forgot to set timeout; reused an indefinite lock in a renewal pattern; mismatch between lock semantics and renewal logic.

Understand the failure class

Related errors


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

Appendix: source

Thrown at redis/asyncio/lock.py:312

            raise LockNotOwnedError("Cannot release a lock that's no longer owned")

    def extend(
        self, additional_time: Number, replace_ttl: bool = False
    ) -> Awaitable[Literal[True]]:
        """
        Adds more time to an already acquired lock.

        ``additional_time`` can be specified as an integer or a float, both
        representing the number of seconds to add.

        ``replace_ttl`` if False (the default), add `additional_time` to
        the lock's existing ttl. If True, replace the lock's ttl with
        `additional_time`.
        """
        if self.local.token is None:
            raise LockError("Cannot extend an unlocked lock")
        if self.timeout is None:
            raise LockError("Cannot extend a lock with no timeout")
        return self.do_extend(additional_time, replace_ttl)

    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.
        """

View on GitHub (pinned to 6a6b581b48)