Skip to content

v2.5.0

Choose a tag to compare

@bpacholek bpacholek released this 11 Jul 06:51
· 119 commits to main since this release

This release closes all 21 findings of the 2026-07-11 six-dimension review (#123-#143) plus the three in-flight fixes from the previous cycle (#112, #114, #116): every silent message-loss path found in the connection layer is fixed, the client survives abandonment and terminal closes without leaking, JetStream/KV behavior moved to spec/ADR parity, and the hot paths got measured performance work.

Message-loss fixes

  • The reconnect buffer is no longer cleared before the flush write - a flush failure mid-recovery used to silently destroy every publish accepted during the reconnect window (#123).
  • Transport write() on a closed/never-connected socket throws instead of silently "succeeding" while sending nothing; recovery flips the connection off Open before its first await so concurrent publishes buffer instead of racing the dying socket (#124).
  • The reconnect handshake starts from a clean protocol parser - a drop mid-payload could previously poison every reconnect attempt against a healthy server until the budget exhausted (#125).
  • One failing frame no longer discards the other frames parsed from the same TCP chunk (slow-consumer overflow, failing PONG reply, fatal -ERR); the heartbeat read now reports fatal frames instead of swallowing them (#128).
  • subscribeQueue() buffers deliveries that arrive before the queue object exists (#129).
  • Atomic batch publish fails loudly with UnsupportedFeatureException on servers without batch support instead of silently storing the batch message-by-message (#130).

Lifecycle & memory

  • An abandoned open connection is now garbage-collectable (the ping timer holds it weakly) and a destructor closes the socket - zombie clients no longer PING forever and steal queue-group deliveries (#126).
  • Every terminal transition to Closed releases runtime state; a manual connect() after reconnect exhaustion starts clean instead of resurrecting dead-epoch subscriptions as duplicate-delivering ghosts (#127).
  • Bucket wrapper caches, terminal-failure sockets, and post-commit batch payloads no longer accumulate (#133).

Spec / interop

  • Stream and consumer names are validated client-side (nats.go parity) - dotted names no longer corrupt $JS.API subjects or silently read a sibling stream via direct get (#131).
  • ADR conformance roundup: idle_heartbeat honored and unknown pull-request keys rejected, ordered consumers pin num_replicas: 1, KV buckets are created with deny_delete/discard: new, KV keys enforce the ADR-8 charset, HPUB is guarded by the server's headers capability, service stats report real UTC, and the README documents the NATS 2.9+ consumer-management floor (#132).
  • Slow-consumer drops in SubscriptionQueue are observable via droppedCount() and the error listener; disconnect/unsubscribe backlog-discard semantics are documented (#134).

Performance (measured against a live NATS 2.12)

  • Concurrent requests park instead of polling at 1 kHz: 200 in-flight requests idling 300 ms dropped from 425 ms CPU (a full core) to 92 ms (#135).
  • Outbound writes are single-hop and JetStream acks lost two of their three fiber hops: +18% serial publish throughput, ~2x ack throughput (#136).
  • The reconnect resubscribe replay is one write + one bounded drain instead of ~5 ms x subscriptions (#137).
  • Atomic batch commits coalesce intermediates into 512 KiB segments: 1000-message commit ~2.6x faster (#138).
  • Push/KV-watch deliveries dropped a per-message fiber hop and duplicate header parses: ~12% CPU end to end (#139); inbound parsing micro-overheads trimmed (#140).

Testing & CI

  • First live-server reconnect coverage: a severing transport kills a real TCP session mid-idle/mid-traffic and asserts recovery and post-reconnect delivery (#141).
  • Unit gate ~12 s faster (fractional ping intervals - pingIntervalSeconds now accepts int|float, the release's one [feature]), ~250 rotted line-number pins fixed, behat tolerates exception subclasses (#142).
  • New nightly mutation-test workflow (03:17 UTC daily, 90% MSI gate); composer infection no longer dies at Composer's 300 s process timeout.
  • README corrections (#143) and the atomic-batch version-detection documentation rewrite (#130).

Full details in CHANGELOG.md. All 21 fixes shipped with falsifiability-verified regression tests (each demonstrated to fail against the pre-fix code) and were gated on phpstan level 8, the unit suite, and the live integration suite against NATS 2.12.