/// <summary> /// Prepares and sets up the observables and subjects used, particularly /// <see cref="ListChanges"/>, <see cref="INotifyObservableCountChanges.CountChanges"/> and <see cref="INotifyObserverExceptions.ObserverExceptions"/>. /// </summary> private void SetupObservablesAndObserversAndSubjects() { ListChangesObserver = _listChangesSubject.NotifyOn(Scheduler); // then connect to InnerList's ListChanged Event _innerListChangedRelevantListChangedEventsForwader = System.Reactive.Linq.Observable.FromEventPattern <ListChangedEventHandler, ListChangedEventArgs>( handler => InnerList.ListChanged += handler, handler => InnerList.ListChanged -= handler) .TakeWhile(_ => !IsDisposing && !IsDisposed) .SkipContinuouslyWhile(_ => !IsTrackingChanges) .Where(eventPattern => eventPattern?.EventArgs != null) .Select(eventPattern => eventPattern.EventArgs.ToObservableListChange(InnerList, this)) .ObserveOn(Scheduler) .Subscribe( NotifySubscribersAboutListChanges, exception => { // ToDo: at this point this instance is practically doomed / no longer forwarding any events & therefore further usage of the instance itself should be prevented, or the observable stream should re-connect/signal-and-swallow exceptions. Either way.. not ideal. var observerException = new ObserverException( $"An error occured notifying observers of this {this.GetType().Name} - consistency and future notifications are no longer guaranteed.", exception); ObserverExceptionsObserver.OnNext(observerException); }); }
private void OnNext(ProtoMessage protoMessage) { foreach (var(_, observer) in _observers) { try { var message = MessageFactory.GetMessage(protoMessage); if (protoMessage.HasClientMsgId || message == null) { observer.OnNext(protoMessage); } if (message != null) { observer.OnNext(message); } } catch (Exception ex) { var observerException = new ObserverException(ex, observer); OnError(observerException); } } }
public void ValidateConstructor() { Exception exception = new Exception("randomMessage"); ObserverException ex = new ObserverException(exception); Assert.AreEqual(exception.Message, ex.InnerException.Message); Assert.AreEqual(exception, ex.InnerException); }
public void ValidateSerialization_NullFields() { var originalException = new ObserverException(null); var buffer = new byte[4096]; var formatter = new BinaryFormatter(); var stream1 = new MemoryStream(buffer); var stream2 = new MemoryStream(buffer); formatter.Serialize(stream1, originalException); var deserializedException = (ObserverException)formatter.Deserialize(stream2); Assert.Equal(originalException.Message, deserializedException.Message); Assert.Null(deserializedException.InnerException); }
public void ValidateSerialization_AllFields() { Exception exception = new Exception("randomMessage"); ObserverException originalException = new ObserverException(exception); byte[] buffer = new byte[4096]; BinaryFormatter formatter = new BinaryFormatter(); MemoryStream stream1 = new MemoryStream(buffer); MemoryStream stream2 = new MemoryStream(buffer); formatter.Serialize(stream1, originalException); ObserverException deserializedException = (ObserverException)formatter.Deserialize(stream2); Assert.AreEqual(originalException.Message, deserializedException.Message); Assert.AreEqual(originalException.InnerException.Message, deserializedException.InnerException.Message); }
public async Task ThrowsOnFailedCustomSerializer() { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(1000); Mock <ChangeFeedObserver <MyDocument> > mockObserver = new Mock <ChangeFeedObserver <MyDocument> >(); Mock <PartitionCheckpointer> mockCheckpointer = new Mock <PartitionCheckpointer>(); Mock <FeedIterator> mockIterator = new Mock <FeedIterator>(); mockIterator.Setup(i => i.FetchNextSetAsync(It.IsAny <CancellationToken>())).ReturnsAsync(GetResponse(HttpStatusCode.OK, true)); mockIterator.SetupSequence(i => i.HasMoreResults).Returns(true).Returns(false); CustomSerializerFails serializer = new CustomSerializerFails(); FeedProcessorCore <MyDocument> processor = new FeedProcessorCore <MyDocument>(mockObserver.Object, mockIterator.Object, FeedProcessorCoreTests.DefaultSettings, mockCheckpointer.Object, serializer); ObserverException caughtException = await Assert.ThrowsExceptionAsync <ObserverException>(() => processor.RunAsync(cancellationTokenSource.Token)); Assert.IsInstanceOfType(caughtException.InnerException, typeof(CustomException)); }
public async Task ThrowsOnFailedCustomSerializer() { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(1000); Mock <PartitionCheckpointer> mockCheckpointer = new Mock <PartitionCheckpointer>(); Mock <FeedIterator> mockIterator = new Mock <FeedIterator>(); mockIterator.Setup(i => i.ReadNextAsync(It.IsAny <CancellationToken>())).ReturnsAsync(GetResponse(HttpStatusCode.OK, true)); mockIterator.SetupSequence(i => i.HasMoreResults).Returns(true).Returns(false); CustomSerializerFails serializer = new CustomSerializerFails(); ChangesHandler <dynamic> handler = (changes, cancelationToken) => Task.CompletedTask; ChangeFeedObserverFactoryCore <dynamic> factory = new ChangeFeedObserverFactoryCore <dynamic>(handler, new CosmosSerializerCore(serializer)); FeedProcessorCore processor = new FeedProcessorCore(factory.CreateObserver(), mockIterator.Object, FeedProcessorCoreTests.DefaultSettings, mockCheckpointer.Object); ObserverException caughtException = await Assert.ThrowsExceptionAsync <ObserverException>(() => processor.RunAsync(cancellationTokenSource.Token)); Assert.IsInstanceOfType(caughtException.InnerException, typeof(CustomException)); }
/// <summary> /// Prepares and sets up the observables and subjects used, particularly /// <see cref="_collectionChangesSubject"/>, <see cref="_countChangesSubject"/> and <see cref="_observerExceptionsSubject"/> /// but also internally used RX subscriptions for <see cref="IBindingList.ListChanged"/> and somewhat hack-ish /// 'Count' and 'Items[]' <see cref="INotifyPropertyChanged"/> events on <see cref="CountChanges"/> and <see cref="CollectionChanges"/> /// occurrences (for WPF / Binding) /// </summary> private void SetupObservablesAndObserversAndSubjects() { ObserverExceptionsObserver = _observerExceptionsSubject.NotifyOn(Scheduler); CollectionChangesObserver = _collectionChangesSubject.NotifyOn(Scheduler); CountChangesObserver = _countChangesSubject.NotifyOn(Scheduler); // then connect to InnerList's ListChanged Event _innerListChangedRelevantCollectionChangedEventsForwader = System.Reactive.Linq.Observable.FromEventPattern <ListChangedEventHandler, ListChangedEventArgs>( handler => InnerList.ListChanged += handler, handler => InnerList.ListChanged -= handler) .TakeWhile(_ => !IsDisposing && !IsDisposed) .SkipContinuouslyWhile(_ => !IsTrackingChanges) .Where(eventPattern => eventPattern?.EventArgs != null) .SelectMany(eventPattern => eventPattern.EventArgs.ToObservableCollectionChanges(InnerList)) .ObserveOn(Scheduler) .Subscribe( NotifyObserversAboutCollectionChanges, exception => { // ToDo: at this point this instance is practically doomed / no longer forwarding any events & therefore further usage of the instance itself should be prevented, or the observable stream should re-connect/signal-and-swallow exceptions. Either way.. not ideal. var observerException = new ObserverException( $"An error occured notifying observers of this {this.GetType().Name} - consistency and future notifications are no longer guaranteed.", exception); ObserverExceptionsObserver.OnNext(observerException); }); // 'Count' and 'Item[]' PropertyChanged events are used by WPF typically via / for ObservableCollections, see // http://referencesource.microsoft.com/#System/compmod/system/collections/objectmodel/observablecollection.cs,421 _countChangesCountPropertyChangedForwarder = CountChanges .ObserveOn(Scheduler) .Subscribe(_ => RaisePropertyChanged(nameof(Count))); _collectionChangesItemIndexerPropertyChangedForwarder = CollectionChanges .ObserveOn(Scheduler) .Subscribe(_ => RaisePropertyChanged(ItemIndexerName)); }
/// <summary> /// Notifies all <see cref="CollectionChanges" /> and <see cref="Resets" /> subscribers and /// raises the (observable)collection changed events. /// </summary> /// <param name="observableCollectionChange">The observable collection change.</param> protected virtual void NotifyObserversAboutCollectionChanges(IObservableCollectionChange <T> observableCollectionChange) { if (observableCollectionChange == null) { throw new ArgumentNullException(nameof(observableCollectionChange)); } CheckForAndThrowIfDisposed(); // go ahead and check whether a Reset or item add, -change, -move or -remove shall be signaled // .. based on the ThresholdAmountWhenChangesAreNotifiedAsReset value var actualObservableCollectionChange = (observableCollectionChange.ChangeType == ObservableCollectionChangeType.Reset || IsItemsChangedAmountGreaterThanResetThreshold(1, ThresholdAmountWhenChangesAreNotifiedAsReset)) ? ObservableCollectionChange <T> .Reset : observableCollectionChange; // raise events and notify about collection changes if (actualObservableCollectionChange.ChangeType == ObservableCollectionChangeType.ItemAdded || actualObservableCollectionChange.ChangeType == ObservableCollectionChangeType.ItemRemoved || actualObservableCollectionChange.ChangeType == ObservableCollectionChangeType.Reset) { try { CountChangesObserver.OnNext(Count); } catch (Exception exception) { var observerException = new ObserverException( $"An error occured notifying {nameof(CountChanges)} Observers of this {this.GetType().Name}.", exception); ObserverExceptionsObserver.OnNext(observerException); if (observerException.Handled == false) { throw; } } } try { CollectionChangesObserver.OnNext(actualObservableCollectionChange); } catch (Exception exception) { var observerException = new ObserverException( $"An error occured notifying {nameof(CollectionChanges)} Observers of this {this.GetType().Name}.", exception); ObserverExceptionsObserver.OnNext(observerException); if (observerException.Handled == false) { throw; } } try { RaiseCollectionChanged(actualObservableCollectionChange.ToNotifyCollectionChangedEventArgs()); } catch (Exception exception) { var observerException = new ObserverException( $"An error occured notifying CollectionChanged Subscribers of this {this.GetType().Name}.", exception); ObserverExceptionsObserver.OnNext(observerException); if (observerException.Handled == false) { throw; } } }
/// <summary> /// Stops an ongoing expiration timer (if any) and uses the <paramref name="expirationScheduler" /> to schedule a new expiration /// notification on the given <paramref name="expirationObserver" /> after the given <paramref name="expiry" />. /// </summary> /// <param name="expiry">The expiry.</param> /// <param name="expirationObserver">The expiration observer to notify upon this instance's expiration.</param> /// <param name="observerExceptionsObserver">The <see cref="ObserverException"/> observer to notify, well.. observer exceptions on.</param> /// <param name="expirationScheduler">The expiration scheduler to schedule the expiration notification on.</param> /// <param name="isUpdate">if set to <c>true</c> indicats that this is an update, rather than an initial call (via .ctor).</param> private void CreateOrUpdateExpiration( TimeSpan expiry, IObserver <ObservableCachedElement <TKey, TValue> > expirationObserver, IObserver <ObserverException> observerExceptionsObserver, IScheduler expirationScheduler, bool isUpdate) { if (expirationObserver == null) { throw new ArgumentNullException(nameof(expirationObserver)); } if (observerExceptionsObserver == null) { throw new ArgumentNullException(nameof(observerExceptionsObserver)); } if (expirationScheduler == null) { throw new ArgumentNullException(nameof(expirationScheduler)); } if (expiry < TimeSpan.Zero) { throw new ArgumentOutOfRangeException(nameof(expiry), $"{nameof(expiry)} cannot be negative"); } CheckForAndThrowIfDisposed(); lock (_expiryModificationLocker) { // cancel an existing, scheduled expiration try { _expirySchedulerCancellationDisposable?.Dispose(); _expirySchedulerCancellationDisposable = null; } catch (Exception exception) { var observerException = new ObserverException($"An error occured cancelling a scheduled expiration of a {this.GetType().Name} instance.", exception); observerExceptionsObserver.OnNext(observerException); if (observerException.Handled == false) { throw; } } try { ExpirationScheduler = expirationScheduler; // set 'new' expiry datetime ExpiryDateTime = CalculateExpiryDateTime(expiry); OriginalExpiry = expiry; if (isUpdate) { // and... if (HasExpired) { // reset hasexpired flag HasExpired = false; // and re-add to value / property changed handling and forwarding AddKeyAndValueToPropertyChangedHandling(); } } else { // otherwise, if this is basically the first call to this method & thereby initial start // the value needs to be added to value / property changed handling and forwarding, too AddKeyAndValueToPropertyChangedHandling(); } // and finally schedule expiration on scheduler for given time // IF TimeSpan.MaxValue hasn't been specified if (expiry < TimeSpan.MaxValue) { _expirySchedulerCancellationDisposable = ExpirationScheduler.Schedule(expiry, () => { try { lock (_expiryModificationLocker) { RemoveKeyAndValueFromPropertyChangedHandling(); HasExpired = true; } expirationObserver.OnNext(this); } catch (Exception exception) { var observerException = new ObserverException($"An error occured notifying about the expiration of a {this.GetType().Name} instance.", exception); observerExceptionsObserver.OnNext(observerException); if (observerException.Handled == false) { throw; } } }); } } catch (Exception exception) { var observerException = new ObserverException($"An error occured updating expiration data of a {this.GetType().Name} instance.", exception); observerExceptionsObserver.OnNext(observerException); if (observerException.Handled == false) { throw; } } finally { Interlocked.Increment(ref _expirationChangesCount); } } }
/// <summary> /// Notifies all <see cref="ListChanges" /> and <see cref="INotifyObservableResets.Resets" /> subscribers and /// raises the (observable)collection changed events. /// </summary> /// <param name="observableListChange">The observable list change.</param> protected virtual void NotifySubscribersAboutListChanges(IObservableListChange <T> observableListChange) { // This is similar to what ObservableCollection implements via its NotifyObserversAboutCollectionChanges method, // however: // - no need to handle count-relevant changes because the underlying ObservableCollection takes care of this // - no (extra) (Raise)CollectionChanged call here, again.. already done by the ObservableCollection // - however as 'Move's are only possible for / with ObservableLists, we also raise a PropertyChangedEvent for 'Item[]' (for wpf) in case of a item move(s) if (observableListChange == null) { throw new ArgumentNullException(nameof(observableListChange)); } CheckForAndThrowIfDisposed(); // go ahead and check whether a Reset or item add, -change, -move or -remove shall be signaled // .. based on the ThresholdAmountWhenChangesAreNotifiedAsReset value var actualObservableListChange = (observableListChange.ChangeType == ObservableListChangeType.Reset || IsItemsChangedAmountGreaterThanResetThreshold(1, ThresholdAmountWhenChangesAreNotifiedAsReset)) ? ObservableListChange <T> .Reset(this) : observableListChange; // raise events and notify about list changes try { ListChangesObserver.OnNext(actualObservableListChange); } catch (Exception exception) { var observerException = new ObserverException( $"An error occured notifying {nameof(ListChanges)} observers of this {this.GetType().Name}.", exception); ObserverExceptionsObserver.OnNext(observerException); if (observerException.Handled == false) { throw; } } if (actualObservableListChange.ChangeType == ObservableListChangeType.ItemMoved) { try { RaisePropertyChanged(ItemIndexerName); } catch (Exception exception) { var observerException = new ObserverException( $"An error occured notifying {nameof(PropertyChanged)} subscribers of this {this.GetType().Name}.", exception); ObserverExceptionsObserver.OnNext(observerException); if (observerException.Handled == false) { throw; } } } }