Reliability
Timeouts
Bound each attempt with TimeoutPolicy. What is timed, how RelPrim's own deadline differs from a TimeoutError raised by your client, and how timeouts interact with retries and fallbacks.
A timeout turns a call that hangs into a failure RelPrim can act on: retry it, fall back, or report it. RelPrim timeouts are asyncio deadlines applied to each primary attempt.
Usage
from relprim import TimeoutPolicy, resilient
@resilient(retries=2, timeout=10)
async def call_provider(prompt: str) -> str:
return await provider.generate(prompt)
@resilient(retries=2, timeout=TimeoutPolicy(seconds=2.5))
async def call_fast_provider(prompt: str) -> str:
return await fast_provider.generate(prompt)
timeout accepts a number of seconds or a TimeoutPolicy. In the builder API,
use .with_timeout(TimeoutPolicy(seconds=10)).
seconds must be a finite int or float greater than zero. Like every
duration in RelPrim, it is expressed in seconds.
What is timed
The deadline covers one call of your operation, within one primary attempt:
- each primary attempt gets a fresh deadline, so
retries=2, timeout=10can spend up to thirty seconds in attempts, plus the delays between them; - retry and rate-limit delays between attempts are not part of any deadline;
- validation of the returned value runs after the call and is not timed;
- fallback candidates run without the timeout policy.
To bound the entire execution, wrap the call yourself:
import asyncio
async with asyncio.timeout(30):
result = await call_provider("Summarize this ticket")
When that outer deadline expires, your code receives a plain TimeoutError
from asyncio, not a RelPrim error: RelPrim only reports its own deadline.
RelPrim’s timeout versus your client’s timeout
Only the expiry of the policy’s own deadline raises OperationTimeoutError. A
TimeoutError raised by the operation itself, such as an HTTP client’s read
timeout, propagates unchanged and is handled as an ordinary failure.
| What happened | Attempt failure | Attempt status |
|---|---|---|
The TimeoutPolicy deadline expired |
OperationTimeoutError (.timeout_seconds, .cause) |
timed_out |
Your client raised TimeoutError before the deadline |
That TimeoutError |
failed |
The distinction matters when deciding what to retry. With a narrowed retry_on,
list both if both are transient for you:
from relprim import OperationTimeoutError, resilient
@resilient(retries=2, timeout=10, retry_on=(OperationTimeoutError, TimeoutError))
async def call_provider(prompt: str) -> str:
return await provider.generate(prompt)
When the final attempt timed out, the report’s status is
ExecutionStatus.TIMED_OUT and OperationExecutionError.cause is the
OperationTimeoutError.
Cancellation
When the deadline expires, RelPrim cancels the running call and raises
OperationTimeoutError for that attempt. When the caller is cancelled
instead, asyncio.CancelledError propagates unchanged; it is never reported as
a timeout.
Synchronous code
TimeoutPolicy is async only. Python cannot safely interrupt blocking
synchronous code from the outside, so RelPrim does not pretend to. For
synchronous clients, configure the timeout in the client itself and let RelPrim
retry the exception it raises.
