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