예제 #1
0
        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;
        }
예제 #2
0
        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();
        }
예제 #3
0
        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);
        }
예제 #4
0
        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);
        }
예제 #5
0
        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> >());
        }
예제 #6
0
        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);
        }
예제 #7
0
        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;
            }
        }