Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

One Job, Fully Traced

1. The backend advertises readiness

CallAllUnordered first polls futures already in flight. It then asks the backend and the composed Tower service whether they are ready. Only after both say yes does it call Backend::poll_next.

This order matters. A concurrency-limit layer can return Pending, so the worker does not claim another durable job until it has capacity to own it.

2. Compact storage becomes typed input

The backend yields a Task<Compact, Connection, Id>. Its associated Codec decodes the compact representation into Args. Storage chooses identifiers, connections, serialization, queue semantics, and backend middleware; the worker does not know whether the task came from memory, Redis, SQL, or another broker.

3. The task enters the service stack

The worker composes worker context, user layers, backend middleware, readiness, and tracking around the handler service. TaskFn adapts the function to Service<Task<...>>.

For this handler:

async fn send(job: Email, state: Data<Arc<App>>, attempt: Attempt) -> Result<(), Error>

the payload Email is moved into job. Each additional argument implements FromRequest<Task<...>> and is produced from &Task before the function is called. The resulting values are owned across .await; no borrow of the worker or backend is held by the handler.

4. The future is polled

The function returns a future. parallelize(executor) can hand that future to an injected executor such as tokio::spawn; the core is not coupled to Tokio. The worker keeps the resulting service futures in FuturesUnordered, so completions are handled in completion order.

5. Completion becomes lifecycle state

IntoResponse normalizes the handler output. Tracking updates attempt and in-flight state. Backend-provided acknowledgement middleware observes the result and performs persistence-specific completion work. Its acknowledgement future is tracked too, so graceful shutdown cannot report completion while an acknowledgement is still pending.

The deepest invariant is:

capacity → dequeue → decode → extract → execute → acknowledge

Every arrow transfers responsibility; none is merely a convenient call order.