Skip to main content

Settlement

What ack, drop, retry and retry_after become on NATS: an acknowledgement the JetStream server acts on, and copies the runtime publishes on Core.

Each outcome of a handler on a JetStream delivery: what the delivery sends and what the server doesHandlerSentServerack()+ACKfinished,never sentagainretry()-NAKsent againnow, delivered+ 1retry_after(d)-NAK{"delay": …}held for d,then sent again,delivered + 1drop()+TERMended, neversent againno outcomebefore ack_waitnothingsent again afterack_wait,delivered + 1On Core NATS every outcome reports AckError::Unsupported:the server settles nothing
The diagram as text
  1. Handler: ack(); retry(); retry_after(d); drop(); no outcome before ack_wait
  2. Sent: +ACK; -NAK; -NAK {"delay": …}; +TERM; nothing; On Core NATS every outcome reports AckError::Unsupported: the server settles nothing
  3. Server: finished, never sent again; sent again now, delivered + 1; held for d, then sent again, delivered + 1; ended, never sent again; sent again after ack_wait, delivered + 1
  4. ack() → +ACK
  5. +ACK → finished, never sent again
  6. retry() → -NAK
  7. -NAK → sent again now, delivered + 1
  8. retry_after(d) → -NAK {"delay": …}
  9. -NAK {"delay": …} → held for d, then sent again, delivered + 1
  10. drop() → +TERM
  11. +TERM → ended, never sent again
  12. no outcome before ack_wait → nothing
  13. nothing → sent again after ack_wait, delivered + 1

A JetStream delivery settles on the server. The answer goes to the delivery's acknowledgement subject, and the consumer acts on it as Consumers draws. A retry_after puts the delay into the negative acknowledgement itself. The server holds the message and delivers it again on the same consumer, with the same stream sequence and a delivered count one higher. Nothing is published, and no copy is made.

A Core delivery has nothing to settle. ack, retry and drop report AckError::Unsupported, which the runtime logs. The server keeps nothing to deliver again, and the runtime brings a message back only as a copy it publishes. That is at-most-once delivery.

OutcomeCore NATS
ack, dropAckError::Unsupported
retry()AckError::Unsupported; under a declared cap, a copy published at once
retry_after(d)a copy published after d
no outcomenothing

Retries​

A retry on NATS: the attempt cap decides between bringing the message back and the dead-letter subjectBelow the capAt the capA deliveryJetStream counts its deliveries; Core carries a headerThe handler returnsretry_after(delay), or retry() under a declarationmax_attempts: is this attempt below the cap?JetStream:-NAK with the delay,the server holds itCore subject:a copy publishedto the subjectCore pattern:a copy to the subjectthe mount site namesDead-letter subject:the deliverypublished as itarrivedNo dead letter:+TERM on JetStream,nothing moreon CoreA Core copy waits for its delay inside the process:a stop before the timer fires loses it
The diagram as text
  1. Below the cap: JetStream: -NAK with the delay, the server holds it; Core subject: a copy published to the subject; Core pattern: a copy to the subject the mount site names
  2. At the cap: Dead-letter subject: the delivery published as it arrived; No dead letter: +TERM on JetStream, nothing more on Core
  3. A delivery JetStream counts its deliveries; Core carries a header → The handler returns retry_after(delay), or retry() under a declaration: handled
  4. The handler returns retry_after(delay), or retry() under a declaration → max_attempts: is this attempt below the cap?: a retry
  5. max_attempts: is this attempt below the cap? → Below the cap: below
  6. max_attempts: is this attempt below the cap? → At the cap: at the cap
  7. A Core copy waits for its delay inside the process: a stop before the timer fires loses it

The cap is declared at the mount site with max_attempts, and the first delivery counts as one. On JetStream the count is the server's own delivered, so a delivery that ran past ack_wait counts too. On Core the runtime counts the copies it published, in the copy's x-ruststream-retry-count header.

Below the cap, each model brings the message back its own way. JetStream redelivers it from the stream. On Core the runtime publishes a copy to the address the descriptor reports. A CoreSubject reports its own subject. A CoreWildcard matches many subjects and publishes to none, so its registration names the subject a copy goes to, or the app refuses to start.

At the cap, the delivery goes to the dead-letter subject as it arrived, payload and headers unchanged. With no dead-letter subject declared, a JetStream delivery is terminated with +TERM, and a Core one is let go.

The crate declares nothing to the server: max_deliver stays unset, because JetStream has no place to send a message that ran out of deliveries. The runtime's path covers both models.