Error handling

leasepool exceptions

LeasePoolError

Base exception for all leasepool-specific errors.

LeasePoolNotStartedError

Raised when acquiring from a manager that has not been started.

LeaseUnavailableError

Raised when wait=False and no executor is available.

LeaseExpiredError

Raised when submitting through a released or expired lease.

UnsupportedBackendError

Raised when requesting a backend that is not available on the current Python version.

Acquire before start

from leasepool import LeasePoolNotStartedError


try:
    await manager.acquire()
except LeasePoolNotStartedError:
    ...

No capacity available

Use wait=False when you want to fail fast instead of waiting:

from leasepool import LeaseUnavailableError


try:
    lease = await manager.acquire(wait=False)
except LeaseUnavailableError:
    # Return HTTP 503, retry later, or enqueue elsewhere.
    ...

Use timeout when you are willing to wait for bounded time:

try:
    lease = await manager.acquire(timeout=2.0)
except TimeoutError:
    ...

Expired leases

After hard expiry, new submissions through a lease raise LeaseExpiredError.

examples/06_lease_expiry_and_revocation.py
"""
Lease expiry and revocation.

A lease has:

- soft expiry: lease_seconds
- hard expiry: lease_seconds + lease_grace_seconds

After hard expiry, new submissions through that lease are rejected.
"""

from __future__ import annotations

import asyncio

from leasepool import LeasedExecutorManager, LeaseExpiredError


def echo(value: str) -> str:
    return value


async def main() -> None:
    manager = LeasedExecutorManager(
        backend="thread",
        max_pools=1,
        min_pools=1,
        workers_per_pool=1,
        lease_grace_seconds=0.1,
        check_interval=60,
    )

    await manager.start()

    try:
        lease = await manager.acquire(
            owner="expiry-demo",
            lease_seconds=0.1,
        )

        print("Lease ID:", lease.lease_id)
        print("Soft expires at:", lease.soft_expires_at)
        print("Hard expires at:", lease.hard_expires_at)

        print("Before expiry:", await lease.run(echo, "ok"))

        await asyncio.sleep(0.25)

        try:
            lease.executor.submit(echo, "too late")
        except LeaseExpiredError as exc:
            print("Submit after hard expiry was rejected:", exc)

        print("Manager stats after expiry handling:", manager.stats())

    finally:
        await manager.stop()


if __name__ == "__main__":
    asyncio.run(main())

Full exception example

examples/11_error_handling.py
"""
Common error handling patterns.

This example demonstrates the library exceptions users are most likely to handle.
"""

from __future__ import annotations

import asyncio

from leasepool import (
    LeasedExecutorManager,
    LeaseExpiredError,
    LeasePoolNotStartedError,
    LeaseUnavailableError,
)


def echo(value: str) -> str:
    return value


async def main() -> None:
    manager = LeasedExecutorManager(
        backend="thread",
        max_pools=1,
        min_pools=1,
        lease_grace_seconds=0.05,
    )

    try:
        await manager.acquire()
    except LeasePoolNotStartedError as exc:
        print("Acquire before start failed:", exc)

    await manager.start()

    try:
        first = await manager.acquire(owner="first")

        try:
            await manager.acquire(owner="second", wait=False)
        except LeaseUnavailableError as exc:
            print("No lease available:", exc)

        await first.release()

        expiring = await manager.acquire(owner="expiring", lease_seconds=0.05)
        executor = expiring.executor

        await asyncio.sleep(0.15)

        try:
            executor.submit(echo, "too late")
        except LeaseExpiredError as exc:
            print("Lease expired:", exc)

    finally:
        await manager.stop()


if __name__ == "__main__":
    asyncio.run(main())