Restore OnError routing for NPC-sourced WhenPropertyChanged emissions#1127
Restore OnError routing for NPC-sourced WhenPropertyChanged emissions#1127dwcullop wants to merge 1 commit into
Conversation
PR reactivemarbles#1111 (WhenPropertyChanged race fix) rewrote the shallow and deep-chain observation paths and, in a follow-up commit, stopped routing downstream exceptions through OnError. Because these emissions originate from an INotifyPropertyChanged callback, an unhandled OnNext throw escaped raw out of the property setter that raised PropertyChanged. Restore the try/catch that routes accessor, chain-walk, and downstream OnNext exceptions through OnError so a subscriber that supplied an error handler observes the failure and the setter stays safe. Per Jake's review point on reactivemarbles#1111, do NOT swallow a throw from OnError itself: an unhandled downstream error (or a rethrowing error handler) still propagates, matching Subject<T>.OnNext semantics. Adds regression tests in WhenPropertyChangedBehaviorFixture covering both the OnNext-routing restore (RED on current main) and the non-swallow decision, for the shallow and deep-chain paths.
|
This will only make the described scenario worse. Instead of getting thrown out of the property setter, the exception will now infinite loop through the stream chain, and hang the entire thread. Throwing exceptions upstream is something that only happens when there is no downstream exception handler, or when there is a defect within an operator. If a consumer can't tolerate exceptions bubbling out of a property setter, they need to introduce proper handling for it, either downstream, or just below the |
Summary
PR #1111 fixed a TOCTOU race in
WhenPropertyChanged/WhenValueChanged, but a follow-up commit in that PR stopped routing downstream exceptions throughOnError. Because these emissions originate from anINotifyPropertyChangedcallback, an unhandledOnNextthrow escaped raw out of the property setter that raisedPropertyChanged: setting a property could throw because some unrelated observer misbehaved.This restores the error routing on both the shallow (
x => x.Prop) and deep-chain (x => x.A.B.Prop) paths.What changed
SinglePropertySubscription.EmitCurrentandDeepChainSubscription.ProcessSignalagain wrap the accessor read, chain walk, and downstreamOnNextin try/catch, routing any exception throughOnError. A subscriber that supplied an error handler now observes the failure instead of it escaping into the setter.OnErroritself. An unhandled downstream error (or a rethrowing error handler) still propagates, matchingSubject<T>.OnNextsemantics. This is the middle ground between the original swallow-everything behavior and the raw-escape behavior that shipped.Behavior
OnNextthrows, has error handlerOnError, setter safeOnNextthrows, no error handlerOnError(no-op after detach), setter safeTests
WhenPropertyChangedBehaviorFixturegains 4 tests (shallow + deep) covering theOnNext-routing restore (RED on current main) and the non-swallow decision. FullBindingsuite: 156 passed, 2 pre-existing skips.