Async, Concurrency, and Backpressure
Apalis uses async for waiting on brokers and handlers, concurrency for multiple in-flight jobs, and an executor only when the application explicitly supplies one.
Ordinary async functions are already the right shape
An async function is a value implementing FnMut(Args...) -> Future.
TaskFn supplies Tower’s poll_ready and call, the generated implementations
extract up to sixteen typed arguments, and IntoResponse normalizes the output.
No handler macro or runtime-owned callback interface is required.
There are two separate concurrency mechanisms
CallAllUnordered can retain many service futures in a FuturesUnordered.
Actual admission is controlled by readiness. .concurrency(n) adds Tower’s
ConcurrencyLimitLayer; when all permits are held, poll_ready becomes
Pending, and the worker stops polling the backend for another task.
.parallelize(tokio::spawn) solves a different problem. Its generic executor
turns a handler future into an independently scheduled future. Apalis accepts a
function with the necessary input/output shape rather than naming Tokio in its
core types.
backend ready? ─┐
service ready? ─┴─ yes → poll one job → push future ─┐
├→ FuturesUnordered
shutdown? ───────── yes → stop admission → drain ───┘
Concurrency is not parallelism
Many network-bound jobs can make progress on one executor thread. They become
parallel only if the executor runs them simultaneously on multiple threads.
CPU-bound or blocking work still needs an appropriate blocking pool; merely
making its function async does not make it cooperative.
Cancellation is resource accounting
WorkerContext::track wraps important futures. Its pinned drop logic decrements
the task count even when a future is cancelled or dropped. Shutdown therefore
tracks ownership, not just successful return paths.
Shutdown is a change in admission policy
Shutdown is a cloneable future backed by shared atomic state and a waker.
Once signalled, readiness refuses new work. CallAllUnordered drains existing
futures, then calls Backend::poll_close so buffered acknowledgements or broker
resources can flush. Monitor coordinates several workers and may impose a
final deadline.