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

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)