Repository navigation
v2.19.0
Minor release. A JetStream pull fetch, the pull consumer and Direct Get now learn at once that the server rejected their inbox subscription for the connection's subscription limit, whichever read meets the -ERR (#175). Before, when another read took the -ERR (in practice the heartbeat's while the SUB write was blocked), a fetch waited out its expiry and returned as an empty pull, and the pull consumer kept issuing pulls that ended at their deadlines.
Upgrading is recommended if your connections run near their subscription limit. There are no API changes for callers; one internal subscribe variant was added. On the wire, each JetStream inbox SUB (pull fetch, pull consumer, Direct Get batch) is now followed by a PING in the same write, and a reconnect replays it with one. symfony-nats-messenger 5.4.1's transport works with this release unchanged (its functional scenarios and examples pass); one of its unit tests counts PING frames as connection checks and sees the new fence PINGs, so it needs adjusting when the messenger moves to this version (symfony-nats-messenger #75).
Fixed
- JetStream inboxes missed a subscription-limit rejection that another read took (#175). The pull fetch (
fetchBatch(),fetchNext()), the pull consumer run and the batched Direct Get subscribe a reply inbox of their own; amaximum subscriptions exceededfor that SUB names no subject, so when another read met it (the heartbeat's while the SUB write was held up, an application'sprocessIncoming()loop's) the operation never learnt of it. The inboxes now carry the shared reply inbox's rule: a PING behind the SUB confirms them, as does a delivery; a limit-ERRread before that, by whichever fiber, treats every unconfirmed inbox as rejected, and the fetch and the Direct Get batch fail at once with aConnectionExceptionnaming the inbox and the limit, the pull consumer run through its fail-fast latch. An own read meeting the-ERRstill fails with the server's error, as before. A reconnect replays a guarded inbox under the same rule, also one whose own SUB write failed. Several inboxes unconfirmed at once (two fetches, a fetch and the reply inbox) cannot be told apart and are all treated as rejected, as the reply inbox already is. Callers that catch onlyJetStreamExceptionaround a fetch or a Direct Get batch now see aConnectionExceptionat the limit, on a connection that stays open. New internal API:subscribeGuarded()andisSubscriptionLimitError()onNatsClient.
Quality gates
- PHPStan level 8.
- 2867 unit tests (with data sets) on PHP 8.2 to 8.5, 149 live integration tests and 48 Behat scenarios.
- All 45 runnable examples executed against a live server.
- Statement coverage 98.82% from the unit suite (95% floor enforced in CI) and 98.88% combined (97% floor).
- Infection covered MSI 92.8% over the 3958 mutants it ran (90% floor enforced in CI); it skipped 3980 of the 7938 it generated as too slow, so the score does not include them.
- Every fix has tests that fail on the old code: the fetch reported an empty pull at its expiry plus a second, the Direct Get batch stalled to its deadline, and the pull consumer kept retiring pulls at theirs. On a real nats-server with
max_subscriptions: 3and AmpSocketTransport, an application loop holding the socket and the fetch's SUB write held up for 0.5 s, the fetch failed 0.501 s in, as soon as its write completed, where it reported an empty pull after 2.504 s before; the connection stayed open and a retry with a free slot got the message. - The change was reviewed for semantics and for interleavings by two independent reviewers, and their findings were applied.
- symfony-nats-messenger 5.4.1 against this release: PHPStan level max, the 52 functional scenarios against live NATS and the 5 examples pass; 527 of its 528 unit tests pass, the one failing being the scripted-server test that counted the new inbox fence PING as a connection check, adjusted in symfony-nats-messenger #75.
The full record is in the CHANGELOG.