Dead letters
dead_letter(name) names where a spent delivery goes; on ZeroMQ that is a publisher on another endpoint, because a name on the own one stays home.
When a retry would go past the cap of max_attempts, the runtime publishes the delivery under the dead-letter name instead, payload and headers as they arrived.
A mount with a dead-letter name and no cap sends every failed delivery there at once.
A cap with no dead-letter name drops the spent delivery with a warning naming the subscription.
A name is frame 0, not an address, and the dead letter leaves through the retry publisher.
On the subscription's own bound queue that publisher refuses jobs.dead with ZmqError::Send, and the delivery is dropped with a warning.
On the subscription's own bound fan-out jobs.dead matches the prefix jobs and comes back to the subscription that gave up on it.
So a dead letter leaves through a publisher on another endpoint, set with .out_retry(..).
The retry copies leave through that publisher as well, so such a mount sends every failed delivery to the dead letters, with no retry first.
A handler that should see its retries again keeps .out_retry(..) on its own endpoint and declares no dead-letter name.
Retries and dead letters parks failed invoices on a queue of their own.
Example
A job that fails leaves for a second queue the service binds, where park reads it under jobs.dead.
- Macros
- Manual
#[ruststream::app]
fn app() -> impl App {
let dead = ZmqQueue::new(ZmqEndpoint::bind("tcp://0.0.0.0:5561")).bindable();
let parked = dead.bind(Publish);
RustStream::new(AppInfo::new("worker", "0.1.0"))
.with_broker_labeled(
"worker",
ZmqQueue::new(ZmqEndpoint::bind("tcp://0.0.0.0:5555")),
|b| {
// Every failed job leaves for the second queue, under the name it is read by.
b.include(work).dead_letter("jobs.dead").out_retry(parked);
},
)
.with_broker_labeled("dead", dead, |b| {
b.include(park);
})
}
fn app() -> impl App {
let dead = ZmqQueue::new(ZmqEndpoint::bind("tcp://0.0.0.0:5561")).bindable();
let parked = dead.bind(Publish);
RustStream::new(AppInfo::new("worker", "0.1.0"))
.with_broker_labeled(
"worker",
ZmqQueue::new(ZmqEndpoint::bind("tcp://0.0.0.0:5555")),
|b| {
// Every failed job leaves for the second queue, under the name it is read by.
b.include(subscriber("jobs", Work).build())
.dead_letter("jobs.dead")
.out_retry(parked);
},
)
.with_broker_labeled("dead", dead, |b| {
b.include(subscriber("jobs.dead", Park).build());
})
}