/// <summary> /// Unregisters an observable. This method is invoked by an observable. /// </summary> /// <param name="topic">The topic.</param> /// <param name="entityId">The entity id of the observable.</param> /// <param name="observers"> /// A list of observers in the same cluster node. This field is optional. /// When the list if not null or empty, the observer will forward the message to each observer which EntityId is in the /// list. /// </param> /// <returns>The asynchronous result of the operation.</returns> public async Task UnregisterObservableAsync(string topic, EntityId entityId, IEnumerable <EntityId> observers) { EntityId id = await this.GetEntityIdAsync(); if (id == null) { return; } if (string.IsNullOrWhiteSpace(topic)) { throw new ArgumentException($"The {nameof(topic)} parameter cannot be null.", nameof(topic)); } if (entityId == null) { throw new ArgumentException($"The {nameof(entityId)} parameter cannot be null.", nameof(entityId)); } for (int k = 1; k <= ConfigurationHelper.MaxQueryRetryCount; k++) { try { ConditionalValue <Dictionary <Uri, EntityId> > topicState = await this.StateManager.TryGetStateAsync <Dictionary <Uri, EntityId> >(topic); if (!topicState.HasValue) { ActorEventSource.Current.Message( $"Observer not registered to the specified topic.\r\n[Observable]: {entityId}\r\n[Observer]: {id}\r\n[Publication]: Topic=[{topic}]"); return; } Dictionary <Uri, EntityId> observableDictionary = topicState.Value; if (!observableDictionary.ContainsKey(entityId.EntityUri)) { ActorEventSource.Current.Message( $"Observer not registered to the specified observable.\r\n[Observable]: {entityId}\r\n[Observer]: {id}\r\n[Publication]: Topic=[{topic}]"); return; } observableDictionary.Remove(entityId.EntityUri); if (!observableDictionary.Any()) { await this.StateManager.TryRemoveStateAsync(topic); } else { await this.StateManager.SetStateAsync(topic, observableDictionary); } ActorEventSource.Current.Message( $"Observable successfully unregistered.\r\n[Observable]: {entityId}\r\n[Observer]: {id}\r\n[Publication]: Topic=[{topic}]"); break; } catch (FabricTransientException ex) { ActorEventSource.Current.Error(ex); if (k == ConfigurationHelper.MaxQueryRetryCount) { throw new TimeoutException(Constants.RetryTimeoutExhausted); } } catch (AggregateException ex) { foreach (Exception e in ex.InnerExceptions) { ActorEventSource.Current.Error(e); } throw; } catch (Exception ex) { ActorEventSource.Current.Error(ex); throw; } await Task.Delay(ConfigurationHelper.BackoffQueryDelay); } try { if (observers != null) { IList <EntityId> observerList = observers as IList <EntityId> ?? observers.ToList(); if (observerList.Any()) { StringBuilder builder = new StringBuilder($"Observer Proxy:\r\n[From]: {id}"); foreach (EntityId item in observerList) { builder.Append($"\r\n[To]: {item}"); } ActorEventSource.Current.Message(builder.ToString()); List <Task> taskList = new List <Task>(); taskList.AddRange(observerList.Select(observer => ProcessingHelper.UnregisterObservableAsync(topic, observer, entityId, null))); await Task.WhenAll(taskList.ToArray()); } } } catch (AggregateException ex) { foreach (Exception e in ex.InnerExceptions) { ActorEventSource.Current.Error(e); } throw; } catch (Exception ex) { ActorEventSource.Current.Error(ex); throw; } if (this.ObservableUnregistered == null) { return; } try { Delegate[] invocationList = this.ObservableUnregistered.GetInvocationList(); Task[] handlerTasks = new Task[invocationList.Length]; SubscriptionEventArgs args = new SubscriptionEventArgs(topic, entityId); for (int i = 0; i < invocationList.Length; i++) { handlerTasks[i] = ProcessingHelper.ExecuteEventHandlerAsync((Func <SubscriptionEventArgs, Task>)invocationList[i], args); } await Task.WhenAll(handlerTasks); } catch (AggregateException ex) { foreach (Exception e in ex.InnerExceptions) { ActorEventSource.Current.Error(e); } } catch (Exception ex) { ActorEventSource.Current.Error(ex); } }
/// <summary> /// Unregisters an entity as observable for a given topic. /// This method is called by a management service or actor. /// </summary> /// <param name="topic">The topic.</param> /// <param name="useObserverAsProxy">Observable uses one observer for each cluster node as a proxy when true, /// it directly sends the message to all observers otherwise.</param> /// <returns>The asynchronous result of the operation.</returns> public async Task UnregisterObservableActorAsync(string topic, bool useObserverAsProxy) { EntityId id = await this.GetEntityIdAsync(); if (id == null) { return; } if (string.IsNullOrWhiteSpace(topic)) { throw new ArgumentException($"The {nameof(topic)} parameter cannot be null.", nameof(topic)); } ConditionalValue <Dictionary <Uri, ObserverInfo> > topicState = await this.StateManager.TryGetStateAsync <Dictionary <Uri, ObserverInfo> >(topic); if (!topicState.HasValue) { throw new ArgumentException($"{id} is not an observable for Topic=[{topic}]"); } Dictionary <Uri, ObserverInfo> observerDictionary = topicState.Value; try { for (int k = 1; k <= ConfigurationHelper.MaxQueryRetryCount; k++) { try { IRegistryService registryService = ServiceProxy.Create <IRegistryService>(ConfigurationHelper.RegistryServiceUri, new ServicePartitionKey(PartitionResolver.Resolve(topic, ConfigurationHelper.RegistryServicePartitionCount))); await registryService.UnregisterObservableAsync(topic, id); break; } catch (FabricTransientException ex) { ActorEventSource.Current.Error(ex); if (k == ConfigurationHelper.MaxQueryRetryCount) { throw; } } catch (AggregateException ex) { foreach (Exception innerException in ex.InnerExceptions) { ActorEventSource.Current.Error(innerException); } if (k == ConfigurationHelper.MaxQueryRetryCount) { throw; } } catch (Exception ex) { ActorEventSource.Current.Error(ex); if (k == ConfigurationHelper.MaxQueryRetryCount) { throw; } } await Task.Delay(ConfigurationHelper.BackoffQueryDelay); } List <Task> taskList = new List <Task>(); try { if (useObserverAsProxy) { // observers are grouped by NodeName taskList.AddRange( observerDictionary. Select(kvp => kvp.Value.EntityId). GroupBy(e => e.NodeName). Select(groupingByNodeName => ProcessingHelper.GetObserverProxyAndList(groupingByNodeName, true)). Select(tuple => ProcessingHelper.UnregisterObservableAsync(topic, tuple.Item1, id, tuple.Item2))); } else { taskList.AddRange( observerDictionary.Select( observer => ProcessingHelper.UnregisterObservableAsync(topic, observer.Value.EntityId, id, null))); } await Task.WhenAll(taskList.ToArray()); } catch (AggregateException ex) { foreach (Exception e in ex.InnerExceptions) { ActorEventSource.Current.Error(e); } } catch (Exception ex) { ActorEventSource.Current.Error(ex); } await this.StateManager.TryRemoveStateAsync(topic); ActorEventSource.Current.Message($"Observable successfully unregistered.\r\n[Observable]: {id}\r\n[Publication]: Topic=[{topic}]."); } catch (FabricTransientException ex) { ActorEventSource.Current.Error(ex); } catch (AggregateException ex) { foreach (Exception e in ex.InnerExceptions) { ActorEventSource.Current.Error(e); } throw; } catch (Exception ex) { ActorEventSource.Current.Error(ex); throw; } }