Own the Subscription Handle
Table of Contents
+#+created: 2026-10-14 +#+updated: 2026-10-14 +#+pattern_group: ownership
This page defines the nats pattern Own the Subscription Handle in the NATS service patterns language. It specialises graceful shutdown for this bus: the lifetime of a subscription, and the drain that ends it.
1. Also known as
- RAII subscription; the subscription lifetime.
2. Context
Subscribing returns a handle whose destructor unsubscribes. A registrar creates many subscriptions and returns them to the service runner, which keeps them for the life of the process.
3. Problem
Who keeps a subscription alive, and how does it end without losing the messages already delivered to it?
4. Forces
- A handle destroyed at the end of the statement that created it unsubscribes immediately, and the subject goes silent with no error.
- The service must not stop receiving until it is ready to stop.
- A handler may be mid-message when shutdown begins.
- A service with hundreds of subjects must not manage each one by hand.
5. Solution
The handle is a value with a lifetime, returned by the registrar and held by the runner:
std::vector<ores::nats::service::subscription> register_entity_handlers(ores::nats::service::client& nats, ores::database::context ctx, std::optional<ores::security::jwt::jwt_authenticator> v) { std::vector<ores::nats::service::subscription> subs; subs.push_back(nats.queue_subscribe(subject, queue_group, handler)); return subs; // the caller owns them from here } // The runner keeps the whole vector for the service's lifetime. auto subs = register_fn(); // ... and drains before it exits. nats.drain();
Three rules:
- Store every handle. A handle not stored is a subscription that ends when the statement does.
- One owner. The registrar returns them and the runner holds them; nothing copies or discards them.
- Drain, do not close. Drain stops new deliveries, lets in-flight handlers finish, flushes pending publishes and only then closes.
6. Failure handling
| Concern | What the pattern does |
|---|---|
| Errors | A dropped handle fails silently: the subject is simply never delivered. |
| Timeouts | Drain is bounded; a handler that hangs must not hang the shutdown forever. |
| Retries | Unaffected. |
| Idempotency | A drained subscription delivers each in-flight message exactly once. |
| Observability | Registration and drain are logged, so a subject that stopped being served can be tied to a restart. |
7. Pitfalls
- A temporary handle. `nats.queue_subscribe(…)` with no assignment subscribes and immediately unsubscribes. Nothing errors.
- A handle destroyed before the client. The client must outlive its subscriptions.
- Closing instead of draining. In-flight handlers are abandoned and their messages are lost.
- Draining then publishing. A publish issued after drain races the shutdown and may be refused; drain belongs last.
8. Resulting context
- Benefits
- Hundreds of subjects are managed as one value, and a restart is clean.
- Liabilities
- A subscriber that is not stored is invisible, because the failure is silence rather than an error.
9. In ORE Studio
Every registrar returns a `std::vector<subscription>`; the service runner keeps it and calls `drain()` on shutdown. A single-subscription handle, such as a client's watch, is a named local or a member so it lives as long as the work needs it.
10. Related patterns
- Queue-Group Subscription — the subscription being owned.
- Acknowledge What Was Handled — why draining rather than closing matters to a durable consumer.
11. Sources
- NATS documentation, Drain & Shutdown — close against drain, and the per-subscription drain.
- NATS documentation, Slow Consumers — what an unattended subscription does under load.
12. See also
- NATS service patterns — the structure note that orders this pattern language, and where to read this page in it.