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
- Bounded Request — the deadline this pattern interprets.
- Report a Refusal Off the Body — the producer of the refusal signal.
11. Sources
- NATS documentation, Request-Reply Resilience — the two failures and why they differ.
- NATS documentation, Request-Reply — the `_INBOX` mechanism and the no-responders signal.
12. See also
- NATS service patterns — the structure note that orders this pattern language, and where to read this page in it.