A fork of nats-io/nats.zig — a
Zig client for the NATS messaging system.
This README documents only what the fork changes. For the base library — installation, quick start, pub/sub, request/reply, JetStream, KV, Micro, authentication, TLS, memory ownership, error handling, the full API reference and the examples — see the upstream
nats-io/nats.zigREADME. Everything there applies unless noted below.
- UNIX domain socket (UDS) transport for the
mutech/nats-serverUDS fork — connect withnats+uds:///path/to/socketfor local, zero-TCP messaging. - CPU / memory fixes over the original — no idle busy-spin (idle CPU ~0 %) and runtime-sized buffers instead of oversized fixed preallocation, so many small, mostly-idle clients are affordable.
std.Io-agnostic refactor — the client does all socket I/O through the injectedio: Io, so it runs unchanged onstd.Io.Threaded(the default) or an eventedstd.Iobackend such as zio. The caller chooses the execution model; the library no longer pins a threading model or hand-rollspoll(2)/eventfd. The public API staysstd.Io-idiomatic (io.async/futures,Io.Select,Io.Queue, cancellation, mutexes, timers).
Intent & status. My preference is to land these changes upstream, but the third item is a large enough refactor that upstreaming it is unlikely — so this fork is probably best treated as an alternative NATS client rather than a staging branch. Feedback on the design is very welcome.
By default the library runs on std.Io.Threaded. To run on zio's evented
std.Io (io_uring on Linux), construct a zio.Runtime and pass its io() to
Client.connect — the client is backend-agnostic, nothing else changes. In this
repo the test/bench harness selects the backend at build time with
-Dio_backend=zio (zio is a lazy build.zig.zon dependency, fetched only for
that flag, so consumers of the nats module never pull it in); see
src/io_backend.zig for how the runtime is constructed.
What to pay attention to — these apply to any evented std.Io, and to
std.Io.Threaded on machines with few cores:
- Use
io.concurrent, notio.async, for anything that must run concurrently (a responder loop, a background drain, a task the caller later depends on).io.asyncmay run inline when the async pool is saturated, and a connected client already occupies pool slots — so on a ≤2-core host such anio.asynccall (or anIo.Selectarm, which is dispatched viaio.async) can run inline and deadlock. Useio.concurrent, or size the runtime'sasync_limitaccordingly. - Put timeouts on a wait, never a racing
sleeptask. UseBatch.awaitConcurrent(timeout)for supported operations, or afutexWaitTimeoutdeadline loop — neverIo.Select(op, sleep(T)), whose sleep arm can run inline and block for the full timeout. - Executors. zio's
Runtimedefaults to a multi-threaded executor pool. For many small runtimes (e.g. one per connection) prefer one executor per runtime (.executors = .exact(1)) so you don't spawn a whole pool per client.
Apache 2.0, same as upstream — see LICENSE.
