Repository navigation
v2.11.0
Minor release. A subscription handler that throws no longer fails an unrelated operation (#173).
Operations that read the socket while they wait for a result of their own deliver messages for every subscription on the connection. When one of those messages went to a handler that threw, the operation failed with that handler's exception, even when its own result had already arrived. The failure is now reported through the error listener, the rest of the read is delivered, and the operation completes, as another subscription's overflow has been under SlowConsumerPolicy::Error since 2.9.0. A new option, handlerErrorsFailOperations, brings back the old way operations failed.
This changes what operations throw, so read the upgrade notes first. symfony-nats-messenger 5.4.1 works with this release unchanged.
Upgrade notes
- Another subscription's handler failure is reported, not thrown. When a handler throws while an operation's read delivers to it, the exception goes to the error listener and is logged at error level, and the operation returns its result. Code that caught such an exception from
request(),flush(),rtt(), a JetStream call or aSubscriptionQueuepoll now gets the result instead. With noerrorListenerand no logger the failure is visible nowhere: register anerrorListener, or sethandlerErrorsFailOperations: trueto have operations fail as before. - What still throws.
processIncoming()andreadIncoming()still throw a handler's exception. The messages behind the one whose handler threw, for any subscription, stay queued until a read receives more from the server or a service'srun()loop delivers them, so an operation in another fiber whose result is among them waits for that and can time out, as before. Catch inside the handler what it can handle.- A handler of the operation's own subscription that throws still fails the operation. That is only ever a subscription the library makes for the operation and handles itself, such as a
SubscriptionQueue's or a fetch's, never a handler you pass tosubscribe(), to a push or ordered consumer, or to a watch. - An
-ERRthat fails a read but leaves the connection open (maximum subscriptions exceeded,Invalid Publish Subject, or a permissions violation other than for a publish to or a subscription to a subject) still fails the operation whose read meets it: it names no subscription, and it is often the answer to what the operation itself sent.Invalid Subjectand a permissions violation for a publish to or a subscription to a subject still fail no read: they are only reported, as before.
- The option restores how operations failed, quirks included. With
handlerErrorsFailOperations: true, an operation fails with another subscription's handler exception, as before this release, and a handler'sCancelledExceptioncan again end an operation's wait early: the operation stops waiting or times out, and the exception is not reported. The fix to the delivery after a reconnect applies either way.
Added
NatsOptions::$handlerErrorsFailOperations(defaultfalse): settrueto have operations fail with another subscription's handler exception, as before this release.Service::run()and the background reads report a handler's failure either way, and the delivery after a reconnect delivers the messages behind a failing handler either way.
Changed
-
Operations and another subscription's handler failure (#173). An operation used to fail with the exception of whichever handler its read delivered to:
- a
request()made inside a service endpoint failed with it, so the endpoint answered its requester with aHANDLER_ERRORreply and kept the other handler's message as itslast_error; - a JetStream publish could fail after the server had stored the message, so a retry stored it twice unless it carried the same message id within the stream's duplicate window;
- the messages behind the failing one waited for another read;
- a handler's
CancelledExceptionmade arequest()take it for the end of its own wait: the request read on with its reply left undelivered, and timed out.
This covers
request(),requestMany()and what is built on them (JetStream publish,ackSync(), stream and consumer management, Key/Value and Object Store calls),flush(),rtt(),fetchBatch()/fetchNext(),directGetBatch(), pull consumers, Key/Valuekeys()andhistory(),SubscriptionQueuepolling, and a request waiting for its reply inbox to be confirmed. - a
-
Delivery after a reconnect. It reported the first handler that threw, but stopped there and left the messages behind it queued. An operation whose own read ran the reconnect then waited for a result that had already arrived: a
SubscriptionQueuepoll returned nothing once its timeout ended, and a fetch discarded what was queued for it when it unsubscribed. It now delivers the rest as well, reporting any other handler that throws, whateverhandlerErrorsFailOperationssays.
Quality gates
- PHPStan level 8.
- 2575 unit tests (with data sets), 149 live integration tests and 48 Behat scenarios. Some timing-bound unit tests failed on busy CI runners (#176) and passed on a later attempt of the same job.
- All 45 runnable examples executed against a live server.
- Statement coverage 99.10% from the unit suite (95% floor enforced in CI) and 99.21% combined (97% floor).
- Infection covered MSI 93.9% over the 4079 mutants it ran (90% floor enforced in CI). It skipped the other 3553 of the 7632 it generated, because the tests covering them take longer than its time limit, so the score does not include them.
- Every fix has tests that fail on the old code.
- An independent reviewer checked the change, and every finding was fixed except one older nit: a reported failure is logged without the subscription it came from. Three more reviewers checked these notes claim by claim.
- symfony-nats-messenger 5.4.1's suites pass against this release: PHPStan level max, 528 unit tests, 52 functional scenarios against live NATS and 5 examples.
The full record is in the CHANGELOG.