Skip to main content

Settlement

What ack, retry, retry_after and drop become on SQS: each outcome is one call against the queue, and a retry waits in the queue itself.

Each outcome of a handler, the SQS call it makes, and what happens to the messageOutcomeSQS callEffectackDeleteMessageGoneretry()ChangeMessageVisibility(0)Visible nowretry_after(d)ChangeMessageVisibility(d)Back after d,within 12 hof the receivedropDeleteMessage;at the cap:Visibility(0)Gone, or to thedead-letterqueuedroppedunsettledNo call,extension stopsBack when thetimeout lapses
The diagram as text
  1. Outcome: ack; retry(); retry_after(d); drop; dropped unsettled
  2. SQS call: DeleteMessage; ChangeMessage Visibility(0); ChangeMessage Visibility(d); DeleteMessage; at the cap: Visibility(0); No call, extension stops
  3. Effect: Gone; Visible now; Back after d, within 12 h of the receive; Gone, or to the dead-letter queue; Back when the timeout lapses
  4. ack → DeleteMessage
  5. DeleteMessage → Gone
  6. retry() → ChangeMessage Visibility(0)
  7. ChangeMessage Visibility(0) → Visible now
  8. retry_after(d) → ChangeMessage Visibility(d)
  9. ChangeMessage Visibility(d) → Back after d, within 12 h of the receive
  10. drop → DeleteMessage; at the cap: Visibility(0)
  11. DeleteMessage; at the cap: Visibility(0) → Gone, or to the dead-letter queue
  12. dropped unsettled → No call, extension stops
  13. No call, extension stops → Back when the timeout lapses

retry_after(d) counts the delay in whole seconds, rounded up, so the message never comes back before d. A delay that reaches past the 12 hours from the receive is shortened to end there, and the crate logs a warning that names the queue. A delivery dropped without a settlement makes no call: its extension stops, and the message returns when its current timeout lapses.

SQS has no verb that rejects a message without deleting it, so drop deletes. A queue whose registration declares a redrive policy is the exception. There, a delivery received maxReceiveCount times is returned instead of deleted, because the next receive is what moves it to the dead-letter queue, and a delete would lose it.

The delivery's redelivery_count is the queue's ApproximateReceiveCount, and the first delivery counts as one. It counts every receive, a lapsed timeout included, not only the retries the service asked for.

Retries​

A retry on SQS: the message waits in the queue, and the redrive policy decides on the next receiveA deliveryits receive count says which attempt it isThe handler returnsretry_after(d), or retry()ChangeMessageVisibility(d)the message waits in the queueThe next receive: has the message beenreceived maxReceiveCount times?Not yet:delivered again,the same messageYes: SQS moves itto the dead-letterqueueA drop on the last allowed receive returns the messagewith visibility 0, so SQS moves it rather than lose itThe queue holds the waiting message:a crash of the service loses no retry
The diagram as text
  1. A delivery its receive count says which attempt it is → The handler returns retry_after(d), or retry(): handled
  2. The handler returns retry_after(d), or retry() → ChangeMessageVisibility(d) the message waits in the queue: a retry
  3. ChangeMessageVisibility(d) the message waits in the queue → The next receive: has the message been received maxReceiveCount times?: after d
  4. The next receive: has the message been received maxReceiveCount times? → Not yet: delivered again, the same message: not yet
  5. The next receive: has the message been received maxReceiveCount times? → Yes: SQS moves it to the dead-letter queue: yes
  6. A drop on the last allowed receive returns the message with visibility 0, so SQS moves it rather than lose it
  7. The queue holds the waiting message: a crash of the service loses no retry

A retry never leaves the queue. retry_after(d) makes the message invisible for d, and it comes back as the same message, with the same id and its receive count one higher. Nothing is republished, so the runtime's republish fallback never runs here, and .out_retry(..) does not compile on an SqsQueue. SQS refuses a visibility that ends more than 12 hours after the receive.

max_attempts(n) and dead_letter(q) together are the queue's redrive policy: maxReceiveCount is n and the dead-letter queue is q. The subscription writes the policy onto the queue when it opens, and SQS counts the receives and moves a spent message itself. One half without the other is SqsError::IncompleteRedrive at startup.

The queue holds a waiting message, so a crash of the service loses no retry: the message comes back when its visibility ends, to whichever consumer polls next.