Error handling¶
leasepool exceptions¶
LeasePoolErrorBase exception for all leasepool-specific errors.
LeasePoolNotStartedErrorRaised when acquiring from a manager that has not been started.
LeaseUnavailableErrorRaised when
wait=Falseand no executor is available.LeaseExpiredErrorRaised when submitting through a released or expired lease.
UnsupportedBackendErrorRaised 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.
"""
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())
Broken executors¶
Broken executor failures come from concurrent.futures. They may occur when a
worker initializer fails or a worker process exits unexpectedly.
leasepool does not hide those failures from submitted futures. It does prevent the broken executor from being reused by future leases.
Full exception example¶
"""
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())