Never Let an Exception Cross the Bus
Table of Contents
+#+filetags: :pattern:nats:error-handling: +#+created: 2026-10-14 +#+updated: 2026-10-14 +#+pattern_group: error-handling
This page defines the nats pattern Never Let an Exception Cross the Bus in the NATS service patterns language. It is bus-specific: it follows from how a message callback is delivered, not from any pattern in the literature.
1. Also known as
- The callback boundary; the C-frame rule.
2. Context
Every subscription is delivered by one callback that the NATS client library invokes on its own thread. In C++, that callback is invoked across a C frame, and the library knows nothing of exceptions.
3. Problem
What happens to an exception that leaves a message handler?
4. Forces
- An exception crossing a C frame is undefined behaviour and in practice calls `std::terminate`, killing the process.
- The process death leaves no line in the service's log, so the cause is invisible.
- A handler does real work — decoding, a database call — and all of that can throw.
- A message that kills its consumer also takes every other subscription with it.
5. Solution
The handler reports and returns; nothing escapes. The transport provides the outermost catch, and each handler provides its own inner one:
// The transport's callback: the last line of defence, on a library thread. void on_msg(natsConnection*, natsSubscription*, natsMsg* msg, void* ud) { // read the subject first, cheaply, so the catch below can name it try { cl->handler(extract_message(msg)); } catch (const std::exception& e) { BOOST_LOG_SEV(lg(), error) << "Unhandled exception in handler for '" << subject << "': " << e.what(); } catch (...) { BOOST_LOG_SEV(lg(), error) << "Unknown exception in handler for '" << subject << "'"; } }
Three rules:
- A handler reports a failure, it does not throw. An exception from the
service layer becomes a
failedanswer in the body; see Report a Failure in the Body. - A caught exception is never simply swallowed. The handler replies with the failure, or — where there is no reply subject — logs it as the whole account of what happened.
- A durable handler must not acknowledge by escaping. Under an auto-ack subscription, returning normally acknowledges the message; see Acknowledge What Was Handled.
6. Failure handling
| Concern | What the pattern does |
|---|---|
| Errors | The exception is caught at the boundary and recorded, and the process survives. |
| Timeouts | A caller whose handler threw and did not reply waits out its deadline; the reply is the handler's responsibility. |
| Retries | A core-NATS message is lost; a durable one is redelivered only if it was not acknowledged. |
| Idempotency | Retrying after a throw requires the handler to be safe to run again. |
| Observability | The boundary logs the subject and the exception text, so a crash is replaced by a line. |
7. Pitfalls
- An exception escaping a handler. The service dies with no log line for the cause, taking every other subscription with it.
- Catching and returning silently. The caller waits out its deadline and the log says nothing.
- A catch that acknowledges. On a durable subscription, a caught failure that returns normally is acknowledged as done.
- Reading the message after destroying it. State read in the catch must be captured before the message is freed.
8. Resulting context
- Benefits
- One bad message costs one message, not the whole service.
- Liabilities
- The handler must be written so every path either replies or deliberately does not.
9. In ORE Studio
The callback in `ores.nats` destroys the cnats message before invoking the
handler and catches every exception, logging the subject and the text. Every
generated handler wraps the service call in its own try/catch and replies with
a failed body. Hand-written handlers follow the same shape.
10. Related patterns
- Report a Failure in the Body — what the handler replies instead of throwing.
- Announce Every Handled Message — the log line that replaces a silent crash.
- Acknowledge What Was Handled — why a caught failure must not be acknowledged.
11. Sources
- NATS documentation, Events and callbacks — a message callback runs on a library thread.
12. See also
- NATS service patterns — the structure note that orders this pattern language, and where to read this page in it.