//******************************************************************************************************
// PIConnectionPool.cs - Gbtc
//
// Copyright © 2010, 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/24/2014 - J. Ritchie Carroll
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.Linq;
using GSF;
namespace PIAdapters
{
///
/// Represents a pool of instances.
///
[Obsolete("Pooling connections was a PI-SDK optimization technique, this is not required for AF-SDK implementations.", false)]
public class PIConnectionPool : IDisposable
{
#region [ Members ]
// Constants
///
/// Default value for .
///
public const int DefaultAccessCountPerConnection = 100;
///
/// Default value for .
///
public const int DefaultMinimumPoolSize = 10;
// Events
///
/// Raised when a has been disconnected from server.
///
public event EventHandler> Disconnected;
// Fields
private readonly List m_connectionPool;
private int m_accessCountPerConnection;
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Creates a new .
///
public PIConnectionPool()
: this(DefaultMinimumPoolSize)
{
}
///
/// Creates a new .
///
/// Minimum pool size to maintain.
public PIConnectionPool(int minimumPoolSize)
{
if (minimumPoolSize < 1)
throw new ArgumentOutOfRangeException(nameof(minimumPoolSize));
m_connectionPool = new List();
MinimumPoolSize = minimumPoolSize;
m_accessCountPerConnection = DefaultAccessCountPerConnection;
}
///
/// Releases the unmanaged resources before the object is reclaimed by .
///
~PIConnectionPool()
{
Dispose(false);
}
#endregion
#region [ Properties ]
///
/// Gets minimum pool size for the .
///
public int MinimumPoolSize { get; }
///
/// Gets or sets maximum accessibility count for each .
///
public int AccessCountPerConnection
{
get => m_accessCountPerConnection;
set
{
if (value < 1)
throw new ArgumentOutOfRangeException(nameof(value));
m_accessCountPerConnection = value;
}
}
///
/// Gets current size of connection pool.
///
public int Size => m_connectionPool.Count;
#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)
return;
try
{
if (!disposing)
return;
if (m_connectionPool is not null)
{
foreach (PIConnection connection in m_connectionPool)
{
connection.Disconnected -= connection_Disconnected;
connection.Dispose();
}
m_connectionPool.Clear();
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
}
}
///
/// Gets a connection from the pool or creates a new one if all current connections are being used at peak access.
///
/// Name of the PI server for the adapter's PI connection.
/// Name of the PI user ID for the adapter's PI connection.
/// Password used for the adapter's PI connection.
/// Timeout interval (in milliseconds) for the adapter's connection.
/// A from the pool.
/// Failed to get a pooled PI connection.
public PIConnection GetPooledConnection(string serverName, string userName = null, string password = null, int connectTimeout = PIConnection.DefaultConnectTimeout)
{
PIConnection connection = null;
// We dynamically allocate pooled PI server connections each having a maximum accessibility count.
// PI's threading model can handle many connections each archiving a small volume of points, but
// falls behind under load when archiving a large volume of points from a single connection.
try
{
lock (m_connectionPool)
{
while (connection is null)
{
// Get next connection from the pool with lowest accessibility count
if (m_connectionPool.Count > 0)
{
PIConnection[] availableConnections = m_connectionPool.Where(c => c.AccessCount < m_accessCountPerConnection).ToArray();
if (availableConnections.Length > 0)
connection = availableConnections.Aggregate((currentMin, nextItem) => nextItem is not null && currentMin.AccessCount < nextItem.AccessCount ? currentMin : nextItem);
}
if (connection is null)
{
// Add pooled connections in groups for better distribution
for (int i = 0; i < MinimumPoolSize; i++)
{
// Create a new connection
connection = new PIConnection
{
ServerName = serverName,
UserName = userName,
Password = password,
ConnectTimeout = connectTimeout
};
// Since PI doesn't detect disconnection until an operation is attempted,
// we must monitor for disconnections from the pooled connections as well
connection.Disconnected += connection_Disconnected;
connection.Open();
// Add the new connection to the server pool
m_connectionPool.Add(connection);
}
}
}
// Increment current connection access count
connection.AccessCount++;
}
}
catch (Exception ex)
{
throw new InvalidOperationException(string.Format("Failed to get a pooled PI connection: {0}", ex.Message), ex);
}
return connection;
}
///
/// Returns to the pool.
///
/// to return to the pool.
/// Provided PIConnection does not belong to this connection pool.
public void ReturnPooledConnection(PIConnection connection)
{
if (connection is null)
return;
lock (m_connectionPool)
{
if (!m_connectionPool.Contains(connection))
throw new InvalidOperationException("Provided PIConnection does not belong to this connection pool");
if (connection.AccessCount > 0)
connection.AccessCount--;
if (connection.AccessCount < 1)
{
if (m_connectionPool.Count > MinimumPoolSize && m_connectionPool.Remove(connection))
{
connection.Disconnected -= connection_Disconnected;
connection.Dispose();
}
}
}
}
private void connection_Disconnected(object sender, EventArgs e) =>
Disconnected?.Invoke(this, new EventArgs(sender as PIConnection));
#endregion
}
}