public async Task <StreamSubscriptionHandle <T> > SubscribeAsync( IAsyncObserver <T> observer, StreamSequenceToken token, StreamFilterPredicate filterFunc = null, object filterData = null) { if (token != null && !IsRewindable) { throw new ArgumentNullException("token", "Passing a non-null token to a non-rewindable IAsyncObservable."); } if (logger.IsVerbose) { logger.Verbose("Subscribe Observer={0} Token={1}", observer, token); } await BindExtensionLazy(); IStreamFilterPredicateWrapper filterWrapper = null; if (filterFunc != null) { filterWrapper = new FilterPredicateWrapperData(filterData, filterFunc); } if (logger.IsVerbose) { logger.Verbose("Subscribe - Connecting to Rendezvous {0} My GrainRef={1} Token={2}", pubSub, myGrainReference, token); } GuidId subscriptionId = pubSub.CreateSubscriptionId(myGrainReference, stream.StreamId); await pubSub.RegisterConsumer(subscriptionId, stream.StreamId, streamProviderName, myGrainReference, token, filterWrapper); return(myExtension.SetObserver(subscriptionId, stream, observer, filterWrapper)); }
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)); } if (logger.IsEnabled(LogLevel.Debug)) { logger.Debug("Subscribe Token={Token}", token); } await BindExtensionLazy(); if (logger.IsEnabled(LogLevel.Debug)) { logger.Debug("Subscribe - Connecting to Rendezvous {0} My GrainRef={1} Token={2}", 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; } }