Skip to main content

RECEIVE_COUNT_HEADER

RECEIVE_COUNT_HEADER carries the queue's ApproximateReceiveCount, one on the first delivery, so a handler can tell the last attempt.

The header's name is sqs-receive-count, and its value is the count as decimal text. SQS counts every receive of a message, the one in hand included. A retry or a deferred retry returns the message to the same queue, so the count goes on rising.

A redrive policy caps the same count. After max_attempts receives, SQS moves the message to the dead-letter queue on the receive that follows. A handler that compares the header with the cap knows when it holds the last attempt. SQS calls the count approximate: on a standard queue a message is delivered at least once, and a duplicate receive counts too.

Example​

A declined payment is tried three times. On the third receive the handler sends it to review and acknowledges it, so it never reaches the dead-letter queue.

/// How many times the queue hands a payment over before it moves it to the dead-letter queue.
const ATTEMPTS: u32 = 3;

#[subscriber(SqsQueue::new("payments"))]
async fn charge(
payment: &Payment,
cx: &mut Context<'_>,
Out(review): Out<impl Publisher, Review>,
) -> HandlerOutcome {
if payment.card_accepted {
return HandlerOutcome::ack();
}
let receives: u32 = cx
.headers()
.get(RECEIVE_COUNT_HEADER)
.and_then(|count| str::from_utf8(count).ok()?.parse().ok())
.unwrap_or(1);
if receives < ATTEMPTS {
return HandlerOutcome::retry();
}
// The last attempt: a person looks at the payment before the queue gives up on it.
tracing::warn!(payment.id, receives, "payment declined, sent to review");
match review
.message(payment)
.to("payments-review")
.publish()
.await
{
Ok(()) => HandlerOutcome::ack(),
Err(_) => HandlerOutcome::retry(),
}
}

#[app]
fn app() -> impl App {
RustStream::new(AppInfo::new("payments", "0.1.0")).with_broker(SqsBroker::new(), |b| {
b.include(charge)
.max_attempts(nonzero!(ATTEMPTS))
.dead_letter("payments-dead")
.out(Review, Publish::default())
.build();
})
}