//******************************************************************************************************
// 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
}
}