public async Task Cleanup() { _ = RequestContextExtensions.SuppressCurrentCallChainFlow(); if (logger.IsEnabled(LogLevel.Debug)) { logger.LogDebug("Cleanup() called"); } if (myExtension == null) { return; } var allHandles = myExtension.GetAllStreamHandles <T>(); var tasks = new List <Task>(); foreach (var handle in allHandles) { myExtension.RemoveObserver(handle.SubscriptionId); tasks.Add(pubSub.UnregisterConsumer(handle.SubscriptionId, stream.InternalStreamId)); } try { await Task.WhenAll(tasks); } catch (Exception exc) { logger.LogWarning( (int)ErrorCode.StreamProvider_ConsumerFailedToUnregister, exc, "Ignoring unhandled exception during PubSub.UnregisterConsumer"); } myExtension = null; }
public async Task UnsubscribeAsync(StreamSubscriptionHandle <T> handle) { _ = RequestContextExtensions.SuppressCurrentCallChainFlow(); await BindExtensionLazy(); StreamSubscriptionHandleImpl <T> handleImpl = CheckHandleValidity(handle); if (logger.IsEnabled(LogLevel.Debug)) { logger.LogDebug("Unsubscribe StreamSubscriptionHandle={Handle}", handle); } myExtension.RemoveObserver(handleImpl.SubscriptionId); // UnregisterConsumer from pubsub even if does not have this handle locally, to allow UnsubscribeAsync retries. if (logger.IsEnabled(LogLevel.Debug)) { logger.LogDebug("Unsubscribe - Disconnecting from Rendezvous {PubSub} My GrainRef={GrainReference}", pubSub, myGrainReference); } await pubSub.UnregisterConsumer(handleImpl.SubscriptionId, stream.InternalStreamId); handleImpl.Invalidate(); }
private async Task <StreamSubscriptionHandle <T> > ResumeAsyncImpl( StreamSubscriptionHandle <T> handle, IAsyncObserver <T> observer, IAsyncBatchObserver <T> batchObserver, StreamSequenceToken token = null) { _ = RequestContextExtensions.SuppressCurrentCallChainFlow(); StreamSubscriptionHandleImpl <T> oldHandleImpl = CheckHandleValidity(handle); if (token != null && !IsRewindable) { throw new ArgumentNullException("token", "Passing a non-null token to a non-rewindable IAsyncObservable."); } if (logger.IsEnabled(LogLevel.Debug)) { logger.LogDebug("Resume Token={Token}", token); } await BindExtensionLazy(); if (logger.IsEnabled(LogLevel.Debug)) { logger.LogDebug("Resume - Connecting to Rendezvous {PubSub} My GrainRef={GrainReference} Token={Token}", pubSub, myGrainReference, token); } StreamSubscriptionHandle <T> newHandle = myExtension.SetObserver(oldHandleImpl.SubscriptionId, stream, observer, batchObserver, token, null); // On failure caller should be able to retry using the original handle, so invalidate old handle only if everything succeeded. oldHandleImpl.Invalidate(); return(newHandle); }
public async Task OnNextAsync(T item, StreamSequenceToken token) { if (token != null && !IsRewindable) { throw new ArgumentNullException("token", "Passing a non-null token to a non-rewindable IAsyncBatchObserver."); } if (isDisposed) { throw new ObjectDisposedException(string.Format("{0}-{1}", GetType(), "OnNextAsync")); } _ = RequestContextExtensions.SuppressCurrentCallChainFlow(); if (!connectedToRendezvous) { if (!this.optimizeForImmutableData) { // In order to avoid potential concurrency errors, synchronously copy the input before yielding the // thread. DeliverItem below must also be take care to avoid yielding before copying for non-immutable objects. item = this.deepCopier.Copy(item); } await ConnectToRendezvous(); } await myExtension.DeliverItem(stream.InternalStreamId, item); }
public async Task <IList <StreamSubscriptionHandle <T> > > GetAllSubscriptions() { _ = RequestContextExtensions.SuppressCurrentCallChainFlow(); await BindExtensionLazy(); List <StreamSubscription> subscriptions = await pubSub.GetAllSubscriptions(stream.InternalStreamId, myGrainReference); return(subscriptions.Select(sub => new StreamSubscriptionHandleImpl <T>(GuidId.GetGuidId(sub.SubscriptionId), stream)) .ToList <StreamSubscriptionHandle <T> >()); }
public async Task OnErrorAsync(Exception exc) { if (isDisposed) { throw new ObjectDisposedException(string.Format("{0}-{1}", GetType(), "OnErrorAsync")); } _ = RequestContextExtensions.SuppressCurrentCallChainFlow(); if (!connectedToRendezvous) { await ConnectToRendezvous(); } await myExtension.ErrorInStream(stream.InternalStreamId, exc); }
private async Task <StreamSubscriptionHandle <T> > SubscribeAsyncImpl( IAsyncObserver <T> observer, IAsyncBatchObserver <T> batchObserver, StreamSequenceToken token, string filterData = null) { if (token != null && !IsRewindable) { throw new ArgumentNullException("token", "Passing a non-null token to a non-rewindable IAsyncObservable."); } if (observer is GrainReference) { throw new ArgumentException("On-behalf subscription via grain references is not supported. Only passing of object references is allowed.", nameof(observer)); } if (batchObserver is GrainReference) { throw new ArgumentException("On-behalf subscription via grain references is not supported. Only passing of object references is allowed.", nameof(batchObserver)); } _ = RequestContextExtensions.SuppressCurrentCallChainFlow(); if (logger.IsEnabled(LogLevel.Debug)) { logger.LogDebug("Subscribe Token={Token}", token); } await BindExtensionLazy(); if (logger.IsEnabled(LogLevel.Debug)) { logger.LogDebug("Subscribe - Connecting to Rendezvous {PubSub} My GrainRef={GrainReference} Token={Token}", pubSub, myGrainReference, token); } GuidId subscriptionId = pubSub.CreateSubscriptionId(stream.InternalStreamId, myGrainReference); // Optimistic Concurrency: // In general, we should first register the subsription with the pubsub (pubSub.RegisterConsumer) // and only if it succeeds store it locally (myExtension.SetObserver). // Basicaly, those 2 operations should be done as one atomic transaction - either both or none and isolated from concurrent reads. // BUT: there is a distributed race here: the first msg may arrive before the call is awaited // (since the pubsub notifies the producer that may immideately produce) // and will thus not find the subriptionHandle in the extension, basically violating "isolation". // Therefore, we employ Optimistic Concurrency Control here to guarantee isolation: // we optimisticaly store subscriptionId in the handle first before calling pubSub.RegisterConsumer // and undo it in the case of failure. // There is no problem with that we call myExtension.SetObserver too early before the handle is registered in pub sub, // since this subscriptionId is unique (random Guid) and no one knows it anyway, unless successfully subscribed in the pubsub. var subriptionHandle = myExtension.SetObserver(subscriptionId, stream, observer, batchObserver, token, filterData); try { await pubSub.RegisterConsumer(subscriptionId, stream.InternalStreamId, myGrainReference, filterData); return(subriptionHandle); } catch (Exception) { // Undo the previous call myExtension.SetObserver. myExtension.RemoveObserver(subscriptionId); throw; } }