Queues are declared as an enum, jobs as structs, and derive macros wire
them together so a job can only be enqueued to, or consumed from, the queue it
statically belongs to. Cross-application mix-ups do not compile.
#[derive(Job)] ties a job to one queue set. Producer<Q, _> and
Worker<Q, _> are generic over that set, so enqueueing another
application’s job is a type error rather than a message that quietly lands
on the wrong queue.
Retries you configure once
Exponential backoff with base, factor, cap and full jitter; fixed delay; or
none at all. Set per queue, overridden per job, all in the attribute.
Fatal and retryable failures
A handler says which kind of failure it hit. Retryable goes through the
policy, fatal goes straight to the dead-letter queue.
Deferral for rate limits
A job that cannot run yet is not a job that failed. It waits exactly as
long as the API asks, comes back ahead of the backlog, and spends no
attempt doing it.
An in-memory backend
MemoryBackend runs the whole stack with no broker, honours delays through
tokio::time so tests can use paused virtual time, and exposes what it
saw for assertions.
Graceful shutdown
Stop consuming, finish what was already pulled, await what is in flight.
Nothing is pulled and then discarded.
Reconnection is out of scope. When the connection to the broker drops,
consumer streams end and Worker::run returns an error. Supervise the process
and let it restart.
Hold-queue arguments are not configurable. They follow from the queue name
alone, so every process declares them identically and no two processes can
deadlock each other with PRECONDITION_FAILED.