aio-fluid supports automatic retries for two distinct failure modes: execution failures and rate limiting.
Both are configured per-task via [RetryPolicy][fluid.scheduler.models.RetryPolicy] objects passed to the [@task][fluid.scheduler.task] decorator.
from fluid.scheduler import RetryPolicy, task, TaskRun::: fluid.scheduler.models.RetryPolicy
Set retry on [@task][fluid.scheduler.task] to re-queue the task when its executor raises an exception.
The [TaskRun][fluid.scheduler.TaskRun] is re-queued with an execute_after delay computed from the [RetryPolicy][fluid.scheduler.models.RetryPolicy]; the worker that dequeued it is freed immediately to process other tasks.
from fluid.scheduler import RetryPolicy, task, TaskRun
@task(retry=RetryPolicy(max_attempts=3, wait=2.0, backoff=2.0))
async def fetch(ctx: TaskRun) -> None:
...With backoff=2.0 the delays between attempts are 2s → 4s → 8s.
Use backoff=1.0 (the default) for a fixed delay.
To retry only on specific exception types, pass exceptions:
@task(retry=RetryPolicy(max_attempts=5, wait=1.0, exceptions=(IOError, TimeoutError)))
async def fetch(ctx: TaskRun) -> None:
...Set rate_limit_retry on [@task][fluid.scheduler.task] to re-queue the task when it cannot start because
[max_concurrency][fluid.scheduler.Task.max_concurrency] is already reached.
Without this policy, the [TaskRun][fluid.scheduler.TaskRun] ends immediately in the [rate_limited][fluid.scheduler.TaskState] state.
@task(
max_concurrency=1,
rate_limit_retry=RetryPolicy(max_attempts=5, wait=10.0, backoff=1.5, max_wait=120.0),
)
async def exclusive(ctx: TaskRun) -> None:
...Both retry modes share the same mechanism:
- The [TaskConsumer][fluid.scheduler.TaskConsumer] detects the failure (execution error or concurrency limit).
- It creates a copy of the [TaskRun][fluid.scheduler.TaskRun] with a fresh state and an
execute_aftertimestamp set tonow + delay. - The copy is pushed back onto the Redis queue via the [TaskBroker][fluid.scheduler.TaskBroker].
- The [TaskConsumer][fluid.scheduler.TaskConsumer] is freed immediately — no sleeping.
- When the copy is next dequeued, if
execute_afteris still in the future it is re-scheduled viacall_laterand the worker moves on; otherwise execution proceeds normally.
!!! note
The minimum effective re-queue delay is 5 seconds, regardless of the wait value in the policy.
A call_later is used to avoid busy-looping, and the floor ensures the worker is not called back too aggressively.