Skip to content

v2.20.0

Choose a tag to compare

@bpacholek bpacholek released this 08 Oct 00:54
· 31 commits to main since this release

Minor release. Your own processIncoming() or readIncoming() now first continues the subscriptions whose delivery an earlier read stopped at a handler that threw, before it reads the socket (#186). Before, the failing subscription's later messages stayed queued until the server sent anything, on an otherwise idle connection its next PING, two minutes away by default, and a disconnect() meanwhile discarded them.

Upgrading is recommended if your application runs its own processIncoming() or readIncoming() loop that catches a handler's exception and reads on. There are no API changes. Two behaviours change for such a loop: after a handler failure its next read runs the stopped subscription's handlers, or throws their next failure, at once, instead of blocking on an idle socket; and a loop whose connection drops now delivers that remainder, and throws its next failure, before it notices the drop, where with reconnect off those messages used to be discarded. A read whose cancellation has already fired still continues the remainder before it throws CancelledException. symfony-nats-messenger never calls these reads and works with this release unchanged.

Fixed

  • The rest of a subscription whose handler threw in your own read waited for the server's next bytes (#186). Since 2.13.0 (#177) such a read delivers the other subscriptions' messages, throws the failure and leaves the failing subscription's later messages queued. The application's next processIncoming() or readIncoming() went straight to the socket, so on an otherwise idle connection those messages, and the subscription's next failure, waited for the server's next PING, two minutes away by default, and a disconnect() meanwhile discarded them. Your own read now continues the subscriptions a held handler failure stopped before it reads, in sid order, and then any that a read inside one of those handlers or another fiber's delivery stops meanwhile, with its own rules: a handler that throws there is thrown, one failure per read; otherwise the read reads as before, and its result still counts only what it took off the wire. Only what a handler failure stopped is continued. A subscription whose delivery is under way in another fiber is left to it, a disconnect() already under way when the read starts still discards what is queued, and a reconnect started while one of those handlers awaits is waited for before the new socket is read. A read already waiting on the socket is not woken when another fiber's read stops a subscription; that remainder waits for your next read.

Quality gates

  • PHPStan level 8.
  • 2888 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.79% from the unit suite (95% floor enforced in CI) and 98.86% combined (97% floor).
  • Infection covered MSI 92.9% over the 3910 mutants it ran (90% floor enforced in CI); it skipped 4058 of the 7968 it generated as too slow, so the score does not include them.
  • Every change has tests that fail on the old code. 21 new test cases, decided by the order of events: all but three fail on 2.19.0, and those three pin what must not change. On nats-server 2.12, a catch-and-continue loop got s2 and s3 1 ms after the one write that carried them behind a failing s1, where 2.19.0 left them waiting 3.0 s for the next publish.
  • The change was reviewed for semantics and for interleavings by two independent reviewers, and their findings were applied.
  • symfony-nats-messenger's main branch against this release: PHPStan level max, 528 unit tests, 52 functional scenarios against live NATS and 5 examples pass.

The full record is in the CHANGELOG.