//****************************************************************************************************** // RoutingTables.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 06/30/2011 - J. Ritchie Carroll // Generated original version of source code. // 07/25/2011 - J. Ritchie Carroll // Added code to handle connect on demand adapters (i.e., where AutoStart = false). // 12/20/2012 - Starlynn Danyelle Gilliam // Modified Header. // 02/11/2013 - Stephen C. Wills // Added code to handle queue and notify for adapter synchronization. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Linq; using GSF.Diagnostics; using GSF.Threading; using GSF.Units; namespace GSF.TimeSeries.Adapters { /// /// Represents the routing tables for the Iaon adapters. /// public class RoutingTables : IDisposable { #region [ Members ] // Events /// /// Provides status messages to consumer. /// /// /// is new status message. /// public event EventHandler> StatusMessage; /// /// Event is raised when there is an exception encountered while processing. /// /// /// is the exception that was thrown. /// public event EventHandler> ProcessException; // Fields private InputAdapterCollection m_inputAdapters; private ActionAdapterCollection m_actionAdapters; private OutputAdapterCollection m_outputAdapters; private readonly LongSynchronizedOperation m_calculateRoutingTablesOperation; private volatile MeasurementKey[] m_inputMeasurementKeysRestriction; private readonly IRouteMappingTables m_routeMappingTables; private HashSet m_prevCalculatedConsumers; private HashSet m_prevCalculatedProducers; private bool m_disposed; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// public RoutingTables(IRouteMappingTables mappingTable = null) { Log = Logger.CreatePublisher(GetType(), MessageClass.Framework); Log.InitialStackMessages = Log.InitialStackMessages.Union("ComponentName", GetType().Name); m_prevCalculatedConsumers = new HashSet(); m_prevCalculatedProducers = new HashSet(); m_routeMappingTables = mappingTable ?? new RouteMappingDoubleBufferQueue(); m_routeMappingTables.Initialize(status => OnStatusMessage(MessageLevel.Info, status, "Initialization"), ex => OnProcessException(MessageLevel.Warning, ex, "Initialization")); m_calculateRoutingTablesOperation = new LongSynchronizedOperation(CalculateRoutingTables) { IsBackground = true }; } /// /// Releases the unmanaged resources before the object is reclaimed by . /// ~RoutingTables() { Dispose(false); } #endregion #region [ Properties ] /// /// Log messages generated by an adapter. /// protected LogPublisher Log { get; } /// /// Gets or sets the active . /// public InputAdapterCollection InputAdapters { get { return m_inputAdapters; } set { m_inputAdapters = value; } } /// /// Gets or sets the active . /// public ActionAdapterCollection ActionAdapters { get { return m_actionAdapters; } set { m_actionAdapters = value; } } /// /// Gets or sets the active . /// public OutputAdapterCollection OutputAdapters { get { return m_outputAdapters; } set { m_outputAdapters = value; } } #endregion #region [ Methods ] /// /// Releases all the resources used by the object. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (!m_disposed) { try { if (disposing) { m_inputAdapters = null; m_actionAdapters = null; m_outputAdapters = null; } } finally { m_disposed = true; // Prevent duplicate dispose. } } } /// /// Spawn routing tables recalculation. /// /// Input measurement keys restriction. /// /// Set the to null to use full adapter I/O routing demands. /// public virtual void CalculateRoutingTables(MeasurementKey[] inputMeasurementKeysRestriction) { try { m_inputMeasurementKeysRestriction = inputMeasurementKeysRestriction; m_calculateRoutingTablesOperation.RunOnceAsync(); } catch (Exception ex) { // Process exception for logging OnProcessException(MessageLevel.Info, new InvalidOperationException("Failed to queue routing table calculation: " + ex.Message, ex)); } } private void CalculateRoutingTables() { long startTime = DateTime.UtcNow.Ticks; Time elapsedTime; IInputAdapter[] inputAdapterCollection = null; IActionAdapter[] actionAdapterCollection = null; IOutputAdapter[] outputAdapterCollection = null; bool retry = true; OnStatusMessage(MessageLevel.Info, "Starting measurement route calculation..."); // Attempt to cache input, action, and output adapters for routing table calculation. // This could fail if another thread modifies the collections while caching is in // progress (rare), so retry if the caching fails. // // We don't attempt to lock here because we don't own the collections. while (retry) { try { if ((object)m_inputAdapters != null) inputAdapterCollection = m_inputAdapters.ToArray(); if ((object)m_actionAdapters != null) actionAdapterCollection = m_actionAdapters.ToArray(); if ((object)m_outputAdapters != null) outputAdapterCollection = m_outputAdapters.ToArray(); retry = false; } catch (InvalidOperationException) { // Attempt to catch "Collection was modified; enumeration operation may not execute." } catch (NullReferenceException) { // Catch rare exceptions where IaonSession is disposed during a context switch inputAdapterCollection = null; actionAdapterCollection = null; outputAdapterCollection = null; retry = false; } } try { HashSet consumerAdapters; HashSet producerAdapters; // Get a full list of all producer (input/action) adapters producerAdapters = new HashSet((inputAdapterCollection ?? Enumerable.Empty()) .Concat(actionAdapterCollection ?? Enumerable.Empty())); // Get a full list of all consumer (action/output) adapters consumerAdapters = new HashSet((actionAdapterCollection ?? Enumerable.Empty()) .Concat(outputAdapterCollection ?? Enumerable.Empty())); var producerChanges = new RoutingTablesAdaptersList(m_prevCalculatedProducers, producerAdapters); var consumerChanges = new RoutingTablesAdaptersList(m_prevCalculatedConsumers, consumerAdapters); m_routeMappingTables.PatchRoutingTable(producerChanges, consumerChanges); m_prevCalculatedProducers = producerAdapters; m_prevCalculatedConsumers = consumerAdapters; // Start or stop any connect on demand adapters HandleConnectOnDemandAdapters(new HashSet(m_inputMeasurementKeysRestriction ?? Enumerable.Empty()), inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); elapsedTime = Ticks.ToSeconds(DateTime.UtcNow.Ticks - startTime); int routeCount = m_routeMappingTables.RouteCount; int destinationCount = consumerAdapters.Count; OnStatusMessage(MessageLevel.Info, $"Calculated {routeCount} route{(routeCount == 1 ? "" : "s")} for {destinationCount} destination{(destinationCount == 1 ? "" : "s")} in {elapsedTime.ToString(3)}."); } catch (ObjectDisposedException) { // Ignore this error. Seems to happen during normal // operation and does not affect the result. } catch (Exception ex) { OnProcessException(MessageLevel.Warning, new InvalidOperationException("Routing tables calculation error: " + ex.Message, ex)); } } /// /// This method will directly inject measurements into the routing table and use a shared local input adapter. For /// contention reasons, it is not recommended this be its default use case, but it is necessary at times. /// /// the sender object /// the event arguments public void InjectMeasurements(object sender, EventArgs> measurements) { m_routeMappingTables.InjectMeasurements(sender, measurements); } /// /// Event handler for distributing new measurements in a broadcast fashion. /// /// Event source reference to adapter that generated new measurements. /// Event arguments containing a collection of new measurements. /// /// Time-series framework uses this handler to route new measurements to the action and output adapters; adapter will handle filtering. /// public virtual void BroadcastMeasurementsHandler(object sender, EventArgs> e) { ICollection newMeasurements = e.Argument; m_actionAdapters.QueueMeasurementsForProcessing(newMeasurements); m_outputAdapters.QueueMeasurementsForProcessing(newMeasurements); } /// /// Raises the event and sends this data to the . /// /// The to assign to this message /// New status message. /// A fixed string to classify this event; defaults to null. /// to use, if any; defaults to . /// /// should be a constant string value associated with what type of message is being /// generated. In general, there should only be a few dozen distinct event names per class. Exceeding this /// threshold will cause the EventName to be replaced with a general warning that a usage issue has occurred. /// protected virtual void OnStatusMessage(MessageLevel level, string status, string eventName = null, MessageFlags flags = MessageFlags.None) { try { Log.Publish(level, flags, eventName ?? "CalculateRoutingTables", status); using (Logger.SuppressLogMessages()) StatusMessage?.Invoke(this, new EventArgs(AdapterBase.GetStatusWithMessageLevelPrefix(status, level))); } catch (Exception ex) { // We protect our code from consumer thrown exceptions OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for StatusMessage event: {ex.Message}", ex), "ConsumerEventException"); } } /// /// Raises the event. /// /// The to assign to this message /// Processing . /// A fixed string to classify this event; defaults to null. /// to use, if any; defaults to . /// /// should be a constant string value associated with what type of message is being /// generated. In general, there should only be a few dozen distinct event names per class. Exceeding this /// threshold will cause the EventName to be replaced with a general warning that a usage issue has occurred. /// protected virtual void OnProcessException(MessageLevel level, Exception exception, string eventName = null, MessageFlags flags = MessageFlags.None) { try { Log.Publish(level, flags, eventName ?? "CalculateRoutingTables", exception?.Message, null, exception); using (Logger.SuppressLogMessages()) ProcessException?.Invoke(this, new EventArgs(exception)); } catch (Exception ex) { // We protect our code from consumer thrown exceptions Log.Publish(MessageLevel.Info, "ConsumerEventException", $"Exception in consumer handler for ProcessException event: {ex.Message}", null, ex); } } /// /// Starts or stops connect on demand adapters based on current state of demanded input or output signals. /// /// The set of signals to be produced by the chain of adapters to be handled. /// Collection of input adapters at start of routing table calculation. /// Collection of action adapters at start of routing table calculation. /// Collection of output adapters at start of routing table calculation. /// /// Set the to null to use full adapter routing demands. /// protected virtual void HandleConnectOnDemandAdapters(ISet inputMeasurementKeysRestriction, IInputAdapter[] inputAdapterCollection, IActionAdapter[] actionAdapterCollection, IOutputAdapter[] outputAdapterCollection) { ISet dependencyChain; ISet inputSignals; ISet outputSignals; ISet requestedInputSignals; ISet requestedOutputSignals; if (inputMeasurementKeysRestriction.Any()) { // When an input signals restriction has been defined, determine the set of adapters // by walking the dependency chain of the restriction dependencyChain = TraverseDependencyChain(inputMeasurementKeysRestriction, inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); } else { // Determine the set of adapters in the dependency chain for all adapters in the system dependencyChain = TraverseDependencyChain(inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); } // Get the full set of requested input and output signals in the entire dependency chain inputSignals = new HashSet(dependencyChain.SelectMany(adapter => adapter.InputMeasurementKeys())); outputSignals = new HashSet(dependencyChain.SelectMany(adapter => adapter.OutputMeasurementKeys())); // Turn connect on demand input adapters on or off based on whether they are part of the dependency chain if ((object)inputAdapterCollection != null) { foreach (IInputAdapter inputAdapter in inputAdapterCollection) { if (inputAdapter.Initialized && !inputAdapter.AutoStart) { if (dependencyChain.Contains(inputAdapter)) { requestedOutputSignals = new HashSet(inputAdapter.OutputMeasurementKeys()); requestedOutputSignals.IntersectWith(inputSignals); inputAdapter.RequestedOutputMeasurementKeys = requestedOutputSignals.ToArray(); inputAdapter.Enabled = true; } else { inputAdapter.RequestedOutputMeasurementKeys = null; inputAdapter.Enabled = false; } } } } // Turn connect on demand action adapters on or off based on whether they are part of the dependency chain if ((object)actionAdapterCollection != null) { foreach (IActionAdapter actionAdapter in actionAdapterCollection) { if (actionAdapter.Initialized && !actionAdapter.AutoStart) { if (dependencyChain.Contains(actionAdapter)) { if (actionAdapter.RespectInputDemands) { requestedInputSignals = new HashSet(actionAdapter.InputMeasurementKeys()); requestedInputSignals.IntersectWith(outputSignals); actionAdapter.RequestedInputMeasurementKeys = requestedInputSignals.ToArray(); } if (actionAdapter.RespectOutputDemands) { requestedOutputSignals = new HashSet(actionAdapter.OutputMeasurementKeys()); requestedOutputSignals.IntersectWith(inputSignals); actionAdapter.RequestedOutputMeasurementKeys = requestedOutputSignals.ToArray(); } actionAdapter.Enabled = true; } else { actionAdapter.RequestedInputMeasurementKeys = null; actionAdapter.RequestedOutputMeasurementKeys = null; actionAdapter.Enabled = false; } } } } // Turn connect on demand output adapters on or off based on whether they are part of the dependency chain if ((object)outputAdapterCollection != null) { foreach (IOutputAdapter outputAdapter in outputAdapterCollection) { if (outputAdapter.Initialized && !outputAdapter.AutoStart) { if (dependencyChain.Contains(outputAdapter)) { requestedInputSignals = new HashSet(outputAdapter.InputMeasurementKeys()); requestedInputSignals.IntersectWith(outputSignals); outputAdapter.RequestedInputMeasurementKeys = requestedInputSignals.ToArray(); outputAdapter.Enabled = true; } else { outputAdapter.RequestedInputMeasurementKeys = null; outputAdapter.Enabled = false; } } } } } /// /// Determines the set of adapters in the dependency chain that produces the set of signals in the /// and returns the set of input signals required by the /// adapters in the chain and the set of output signals produced by the adapters in the chain. /// /// The set of signals that must be produced by the dependency chain. /// Collection of input adapters at start of routing table calculation. /// Collection of action adapters at start of routing table calculation. /// Collection of output adapters at start of routing table calculation. protected virtual ISet TraverseDependencyChain(ISet inputMeasurementKeysRestriction, IInputAdapter[] inputAdapterCollection, IActionAdapter[] actionAdapterCollection, IOutputAdapter[] outputAdapterCollection) { ISet dependencyChain = new HashSet(); if ((object)inputAdapterCollection != null) { foreach (IInputAdapter inputAdapter in inputAdapterCollection) { if (inputAdapter.Initialized && !dependencyChain.Contains(inputAdapter) && inputMeasurementKeysRestriction.Overlaps(inputAdapter.OutputMeasurementKeys())) AddInputAdapter(inputAdapter, dependencyChain, inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); } } if ((object)actionAdapterCollection != null) { foreach (IActionAdapter actionAdapter in actionAdapterCollection) { if (actionAdapter.Initialized && !dependencyChain.Contains(actionAdapter) && inputMeasurementKeysRestriction.Overlaps(actionAdapter.OutputMeasurementKeys())) AddActionAdapter(actionAdapter, dependencyChain, inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); } } return dependencyChain; } /// /// Determines the set of adapters in the dependency chain for all adapters in the system which are either not connect or demand or are demanded. /// /// Collection of input adapters at start of routing table calculation. /// Collection of action adapters at start of routing table calculation. /// Collection of output adapters at start of routing table calculation. protected virtual ISet TraverseDependencyChain(IInputAdapter[] inputAdapterCollection, IActionAdapter[] actionAdapterCollection, IOutputAdapter[] outputAdapterCollection) { ISet dependencyChain = new HashSet(); if ((object)inputAdapterCollection != null) { foreach (IInputAdapter inputAdapter in inputAdapterCollection) { if (inputAdapter.Initialized && inputAdapter.AutoStart && !dependencyChain.Contains(inputAdapter)) AddInputAdapter(inputAdapter, dependencyChain, inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); } } if ((object)actionAdapterCollection != null) { foreach (IActionAdapter actionAdapter in actionAdapterCollection) { if (actionAdapter.Initialized && actionAdapter.AutoStart && !dependencyChain.Contains(actionAdapter)) AddActionAdapter(actionAdapter, dependencyChain, inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); } } if ((object)outputAdapterCollection != null) { foreach (IOutputAdapter outputAdapter in outputAdapterCollection) { if (outputAdapter.Initialized && outputAdapter.AutoStart && !dependencyChain.Contains(outputAdapter)) AddOutputAdapter(outputAdapter, dependencyChain, inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); } } return dependencyChain; } // Adds an input adapter to the dependency chain. private void AddInputAdapter(IInputAdapter adapter, ISet dependencyChain, IInputAdapter[] inputAdapterCollection, IActionAdapter[] actionAdapterCollection, IOutputAdapter[] outputAdapterCollection) { HashSet outputMeasurementKeys = new HashSet(adapter.OutputMeasurementKeys()); // Adds the adapter to the chain dependencyChain.Add(adapter); if ((object)actionAdapterCollection != null) { // Checks all action adapters to determine whether they also need to be // added to the chain as a result of this adapter being added to the chain foreach (IActionAdapter actionAdapter in actionAdapterCollection) { if (actionAdapter.Initialized && actionAdapter.RespectInputDemands && !dependencyChain.Contains(actionAdapter) && outputMeasurementKeys.Overlaps(actionAdapter.InputMeasurementKeys())) AddActionAdapter(actionAdapter, dependencyChain, inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); } } if ((object)outputAdapterCollection != null) { // Checks all output adapters to determine whether they also need to be // added to the chain as a result of this adapter being added to the chain foreach (IOutputAdapter outputAdapter in outputAdapterCollection) { if (outputAdapter.Initialized && !dependencyChain.Contains(outputAdapter) && outputMeasurementKeys.Overlaps(outputAdapter.InputMeasurementKeys())) AddOutputAdapter(outputAdapter, dependencyChain, inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); } } } // Adds an action adapter to the dependency chain. private void AddActionAdapter(IActionAdapter adapter, ISet dependencyChain, IInputAdapter[] inputAdapterCollection, IActionAdapter[] actionAdapterCollection, IOutputAdapter[] outputAdapterCollection) { HashSet inputMeasurementKeys = new HashSet(adapter.InputMeasurementKeys()); HashSet outputMeasurementKeys = new HashSet(adapter.OutputMeasurementKeys()); // Adds the adapter to the chain dependencyChain.Add(adapter); if ((object)inputAdapterCollection != null) { // Checks all input adapters to determine whether they also need to be // added to the chain as a result of this adapter being added to the chain foreach (IInputAdapter inputAdapter in inputAdapterCollection) { if (inputAdapter.Initialized && !dependencyChain.Contains(inputAdapter) && inputMeasurementKeys.Overlaps(inputAdapter.OutputMeasurementKeys())) AddInputAdapter(inputAdapter, dependencyChain, inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); } } if ((object)actionAdapterCollection != null) { // Checks all action adapters to determine whether they also need to be // added to the chain as a result of this adapter being added to the chain foreach (IActionAdapter actionAdapter in actionAdapterCollection) { if (actionAdapter.Initialized && !dependencyChain.Contains(actionAdapter)) { if (actionAdapter.RespectInputDemands && outputMeasurementKeys.Overlaps(actionAdapter.InputMeasurementKeys())) AddActionAdapter(actionAdapter, dependencyChain, inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); else if (actionAdapter.RespectOutputDemands && inputMeasurementKeys.Overlaps(actionAdapter.OutputMeasurementKeys())) AddActionAdapter(actionAdapter, dependencyChain, inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); } } } if ((object)outputAdapterCollection != null) { // Checks all output adapters to determine whether they also need to be // added to the chain as a result of this adapter being added to the chain foreach (IOutputAdapter outputAdapter in outputAdapterCollection) { if (outputAdapter.Initialized && !dependencyChain.Contains(outputAdapter) && outputMeasurementKeys.Overlaps(outputAdapter.InputMeasurementKeys())) AddOutputAdapter(outputAdapter, dependencyChain, inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); } } } // Adds an output adapter to the dependency chain. private void AddOutputAdapter(IOutputAdapter adapter, ISet dependencyChain, IInputAdapter[] inputAdapterCollection, IActionAdapter[] actionAdapterCollection, IOutputAdapter[] outputAdapterCollection) { HashSet inputMeasurementKeys = new HashSet(adapter.InputMeasurementKeys()); // Adds the adapter to the chain dependencyChain.Add(adapter); if ((object)inputAdapterCollection != null) { // Checks all input adapters to determine whether they also need to be // added to the chain as a result of this adapter being added to the chain foreach (IInputAdapter inputAdapter in inputAdapterCollection) { if (inputAdapter.Initialized && !dependencyChain.Contains(inputAdapter) && inputMeasurementKeys.Overlaps(inputAdapter.OutputMeasurementKeys())) AddInputAdapter(inputAdapter, dependencyChain, inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); } } if ((object)actionAdapterCollection != null) { // Checks all action adapters to determine whether they also need to be // added to the chain as a result of this adapter being added to the chain foreach (IActionAdapter actionAdapter in actionAdapterCollection) { if (actionAdapter.Initialized && actionAdapter.RespectOutputDemands && !dependencyChain.Contains(actionAdapter) && inputMeasurementKeys.Overlaps(actionAdapter.OutputMeasurementKeys())) AddActionAdapter(actionAdapter, dependencyChain, inputAdapterCollection, actionAdapterCollection, outputAdapterCollection); } } } #endregion } }