The portable core is non-blocking on every platform (ADR 0001, ADR 0002). For JVM callers who want synchronous ergonomics, a JVM-only (.clj) blocking layer sits on top, mirroring the async namespace tree as a parallel subtree: nats-cljc.blocking.core now, and nats-cljc.blocking.jetstream / nats-cljc.blocking.kv / nats-cljc.blocking.object alongside their async twins later. Each blocking namespace uses the same verb names as its async counterpart, so a caller switches semantics by swapping a single require (nats-cljc.core → nats-cljc.blocking.core).
ex-info directly (not a wrapping ExecutionException). Already-synchronous ops (publish, unsubscribe) are re-exported unchanged.subscribe returns a handle, and (take-message handle timeout?) blocks the calling thread for the next message (or a timeout/closed sentinel). It is backed by a bounded BlockingQueue fed by the async handler; the bound supplies backpressure via the promise-returning-handler mechanism (ADR 0007). An optional messages reducible/seq supports doseq/reduce.It ships just after the core — the core's protocol already supports it, and the non-blocking core is the priority.
nats-cljc.blocking namespace — rejected: Phase-2/3 verbs collide (core publish vs JetStream publish; KV/Object get/put), exactly the collision the async side avoids by splitting namespaces. A parallel subtree is required.BlockingQueue.put. Under Road 2 (ADR 0007) that thread is already blocked on the handler's CompletionStage, so this adds no thread-pinning the core doesn't already have — and backlog builds in jnats' own dispatcher queue, keeping its native slow-consumer ceiling (512K msgs / 64 MB) as the ultimate floor. Capacity defaults to 1024, overridable per-subscription via :capacity (a non-positive value throws :invalid-capacity); there is no unbounded escape hatch.take-message returns nil for both timeout and end-of-stream, disambiguated by active? (false once the sub has ended and the buffer is drained). A poison-pill enqueued on teardown wakes any blocked take-message. The ergonomic path is messages, an IReduceInit that terminates on its own.unsubscribe/drain split. unsubscribe (and connection close) clears and poisons the queue — which also unblocks any producer parked in .put, so teardown always makes progress and leaks no dispatcher thread. Graceful drain of a pull sub flushes the buffer and therefore requires concurrent consumption.Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |