Classify Transport Failure

Table of Contents

+#+filetags: :pattern:nats:request: +#+created: 2026-10-14 +#+updated: 2026-10-14 +#+pattern_group: request

This page defines the nats pattern Classify Transport Failure in the NATS service patterns language. It is bus-specific: no outer pattern covers the distinction, because the no-responders signal is a property of NATS.

1. Also known as

  • NATS: no responders (a 503 with no body).
  • The pattern language: Retry depends on the distinction.

2. Context

A request can fail in ways that look alike from a distance and are not alike: the responder did not answer in time, the responder does not exist, the server refused the request, or the responder answered with a failure of its own.

3. Problem

Which of several unlike failures has occurred, so that the caller can do the right thing about each?

4. Forces

  • A timeout means someone is there and is slow.
  • No responders means nobody is there at all, and the server knows it at once.
  • A refusal is an answer: the request was understood and rejected.
  • A body carrying a failed outcome is also an answer, and the request was handled.
  • Retrying all four the same way is wrong in at least two of them.

5. Solution

Branch on the signal, and keep the four apart:

try {
    const auto reply = session.authenticated_request(subject, payload, timeout);
    throw_if_refused(reply, subject);           // refused, not answered
    // ... decode the body; a failed outcome is the service's own answer.
} catch (const ores::nats::service::request_refused_error& e) {
    // Understood and rejected: act on the code (forbidden, bad_request, ...).
} catch (const ores::nats::service::nats_connect_error& e) {
    if (e.kind() == ores::nats::service::nats_error_kind::services_unavailable) {
        // Nobody is listening: the service is down or the subject is wrong.
    }
} catch (const std::runtime_error& e) {
    // Everything else, including a request that timed out.
}

The four outcomes, and what each licenses:

Signal Means The caller may
Reply, no error Answered Decode the body and act on the outcome
Empty body + X-Error Refused before handling Log in, ask for permission, fix the payload
No responders No subscription exists Wait for the service to start; do not hammer
Timeout Subscribed, no answer in the deadline Retry if the work is idempotent
Thrown store error The responder failed after starting Report it; the work may be half done

The distinction is preserved by keeping the transport's signals distinct all the way out: the refusal is its own exception type, and no-responders carries a kind, so neither is flattened into "the request failed".

6. Failure handling

Concern What the pattern does
Errors Each failure reaches the caller as itself, not as a generic transport error.
Timeouts A timeout is distinguishable from no responders, so the retry timing differs.
Retries A timeout may be retried at once; no responders should back off; a refusal must not be retried unchanged.
Idempotency A retry after a timeout re-sends the request; the responder must absorb it.
Observability The caller logs which of the outcomes it saw, so a fleet-wide absence is visible as such.

7. Pitfalls

  • One catch for everything. A refused request is retried as though the service were down.
  • Retrying a refusal. The credential, permission or payload is still wrong.
  • Treating no responders as a timeout. The caller waits out a deadline to learn what the first round trip already said, then retries in a tight loop.
  • Decoding an empty refusal body. It reports a malformed payload for what is really an authorization failure.

8. Resulting context

Benefits
The caller's reaction matches the fault, and an operational fault is legible in the log as its own thing.
Liabilities
Every caller must handle four cases rather than one.

9. In ORE Studio

`request_sync` throws `nats_connect_error` with `nats_error_kind::services_unavailable` for no responders and a distinct runtime error for a timeout; `throw_if_refused` raises `request_refused_error` for a refusal and deliberately excludes the two session-expiry codes, which have their own handling.

10. Related patterns

11. Sources

12. See also

  • NATS service patterns — the structure note that orders this pattern language, and where to read this page in it.

Emacs 29.3 (Org mode 9.6.15)