Thread backend

Use the thread backend for blocking I/O or synchronous libraries that would otherwise block the event loop.

Good uses

  • blocking vendor SDK calls;

  • file I/O;

  • synchronous HTTP clients;

  • synchronous database drivers;

  • small blocking operations from async services.

Example

examples/00_quickstart_thread_backend.py
"""
Quickstart: run one blocking sync function from async code.

This is the most basic `leasepool` usage:

1. create a manager
2. start it
3. acquire a lease
4. run sync work inside the leased executor
5. stop the manager
"""

from __future__ import annotations

import asyncio
import time

from leasepool import ExecutorBackend, LeasedExecutorManager


def blocking_uppercase(value: str) -> str:
    time.sleep(0.2)
    return value.upper()


async def main() -> None:
    manager = LeasedExecutorManager(
        backend=ExecutorBackend.THREAD,
        max_pools=2,
        min_pools=1,
        workers_per_pool=4,
        name_prefix="quickstart-worker",
    )

    await manager.start()

    try:
        async with await manager.acquire(owner="quickstart") as lease:
            result = await lease.run(blocking_uppercase, "hello leasepool")

        print("Result:", result)
    finally:
        await manager.stop()


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

Context manager pattern

examples/01_lease_context_manager.py
"""
Using ExecutorLease as an async context manager.

This is the safest default style. The lease is automatically returned even if
your work raises an exception.
"""

from __future__ import annotations

import asyncio
import time

from leasepool import LeasedExecutorManager


def blocking_add(left: int, right: int) -> int:
    time.sleep(0.1)
    return left + right


def blocking_join(*, first: str, second: str) -> str:
    time.sleep(0.1)
    return f"{first}-{second}"


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

    await manager.start()

    try:
        print("Before acquire:", manager.stats())

        async with await manager.acquire(owner="context-manager-example") as lease:
            print("Lease ID:", lease.lease_id)
            print("Owner:", lease.owner)
            print("Soft expires at monotonic timestamp:", lease.soft_expires_at)
            print("Hard expires at monotonic timestamp:", lease.hard_expires_at)

            number_result = await lease.run(blocking_add, 20, 22)
            keyword_result = await lease.run(
                blocking_join,
                first="hello",
                second="world",
            )

            print("Number result:", number_result)
            print("Keyword result:", keyword_result)
            print("During lease:", manager.stats())

        print("After context exits:", manager.stats())

    finally:
        await manager.stop()


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

Avoid

Do not use the thread backend for heavy pure-Python CPU work if true CPU parallelism is required on Python 3.11. Use the process backend instead.