Fix that a source failure is reported as successful completion to deferred subscriptions - #1141
Open
dwcullop wants to merge 1 commit into
Open
Conversation
A Connect() or Watch() made while notifications are suspended is deferred until the suspension lifts. When the cache's source failed, that deferral reported the failure as a successful completion, so the subscriber's error handling never ran and its data looked complete. Two things caused it. The suspension tracker was disposed on failure, which completes the subject that the deferral is waiting on, and the deferral mapped that completion straight onto observer.OnCompleted. Faulting the subject instead carries the exception, and dropping the Do lets the terminal event travel through the chain rather than bypassing it. SelectMany replaces Select followed by Switch. Take(1) means there is only ever one inner sequence, so the two are equivalent for delivery, but SelectMany propagates the gate's terminal event by itself. That leaves the deferral self-contained rather than dependent on the behaviour of another operator.
dwcullop
force-pushed
the
bugfix/deferred_connect_error_propagation
branch
from
July 27, 2026 14:06
ca725c9 to
9016b46
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1140.
A
Connect()orWatch()made while notifications are suspended is deferred until the suspension lifts. When the cache's source failed, that deferral reported the failure as a successful completion.Same source, same failure, opposite terminal event, decided purely by whether the subscriber happened to connect during a suspension. That is worse than losing the error: the subscriber's error handling never runs,
FinallyandCatchsee a normal end of stream, and the consumer is left believing it holds a complete, valid dataset.Why
Two things combined.
CacheUpdateObserver.OnErrordisposed the suspension tracker, and disposal completes the subject the deferral is waiting on:And the deferral mapped that completion straight onto the observer:
So the failure path completed the gate, and the
Doturned that intoobserver.OnCompleted.The change
Fault(Exception)faults the subject rather than completing it, andOnErrorcalls that instead ofDispose(). The exception now has a path to the deferred subscriber.SelectManyreplacesSelectfollowed bySwitch, and theDois dropped.Take(1)means there is only ever one inner sequence, so the two are equivalent for delivery, butSelectManypropagates the gate's terminal event by itself. The deferral is then self-contained rather than depending on the behaviour of another operator, and the terminal event travels through the chain rather than bypassing it.Watch()had the same shape and is changed the same way.Tests
OnErrorFiresIfCacheFailsWhileSuspendedOnErrorFiresIfCacheFailsWhileWatchIsSuspendedOnErrorFiresIfCacheFailsAfterResumingWhileConnectionWasSuspendedThe first two fail without the change. The existing
OnCompletedFiresIfCacheDisposedWhileSuspendedcovers the completion path, which must keep working and does.Notes
Independent of #1137. That one repairs the cache
Switchoperator, which is what #1136 was really about; this removes the deferral's dependence onSwitchaltogether, so the two do not overlap and can land in either order. #1137 also adds tests toSuspendNotificationsFixture, so a trivial conflict is possible depending on merge order.Related to #1133, which reuses this deferral shape for subscriptions made during an
Edit(). That PR would widen the trigger from suspensions to ordinary edits, so this is worth landing first.One unrelated failure appears on this branch and on
main:ConcurrentSuspendDuringResumeDoesNotCorruptfails when run in isolation. That is #1131, which #1132 addresses.