// ------------------------------------------------------------------
// Status
// 0.00.0000 - 06/17/2002 - ulrich sprick - creation
+// - 05/??/2004 - Glen Parker<glenebob@nwlink.com> rewritten using
+// System.Queue.
using System;
-using Npgsql;
+using System.Collections;
+using System.Threading;
namespace Npgsql
{
- internal class ConnectorPool
+ /// <summary>
+ /// This class manages all connector objects, pooled AND non-pooled.
+ /// </summary>
+ internal class NpgsqlConnectorPool
{
+ /// <summary>
+ /// A queue with an extra Int32 for keeping track of busy connections.
+ /// </summary>
+ private class ConnectorQueue : System.Collections.Queue
+ {
+ /// <summary>
+ /// The number of pooled Connectors that belong to this queue but
+ /// are currently in use.
+ /// </summary>
+ public Int32 UseCount = 0;
+ }
+
/// <value>Unique static instance of the connector pool
/// mamager.</value>
- internal static ConnectorPool ConnectorPoolMgr = new Npgsql.ConnectorPool();
+ internal static NpgsqlConnectorPool ConnectorPoolMgr = new NpgsqlConnectorPool();
- /// <value>List of unused, pooled connectors avaliable to the
+ public NpgsqlConnectorPool()
+ {
+ PooledConnectors = new Hashtable();
+ }
+
+
+ /// <value>Map of index to unused pooled connectors, avaliable to the
/// next RequestConnector() call.</value>
- /// <remarks>Points to the head of a double linked list</remarks>
- internal Npgsql.Connector PooledConnectors;
+ /// <remarks>This hashmap will be indexed by connection string.
+ /// This key will hold a list of queues of pooled connectors available to be used.</remarks>
+ private Hashtable PooledConnectors;
- /// <value>List of used, shared conncetors.</value>
- /// <remarks>Points to the head of a double linked list</remarks>
- private Npgsql.Connector SharedConnectors;
+ /// <value>Map of shared connectors, avaliable to the
+ /// next RequestConnector() call.</value>
+ /// <remarks>This hashmap will be indexed by connection string.
+ /// This key will hold a list of shared connectors available to be used.</remarks>
+ // To be implemented
+ //private Hashtable SharedConnectors;
/// <summary>
- /// Cuts out a connector from the the list it is in.
+ /// Searches the shared and pooled connector lists for a
+ /// matching connector object or creates a new one.
/// </summary>
- /// <param name="Connector">The connector object to be cut out.</param>
- /// <remarks>Shall be replaced if the lists will be based on
- /// Collections.DictionaryBase classs </remarks>
- internal void CutOutConnector( Npgsql.Connector Connector )
+ /// <param name="Connection">The NpgsqlConnection that is requesting
+ /// the connector. Its ConnectionString will be used to search the
+ /// pool for available connectors.</param>
+ /// <returns>A connector object.</returns>
+ public NpgsqlConnector RequestConnector (NpgsqlConnection Connection)
{
- if ( Connector.Prev != null )
- Connector.Prev.Next = Connector.Next;
- if ( Connector.Next != null )
- Connector.Next.Prev = Connector.Prev;
+ NpgsqlConnector Connector;
+
+ if (Connection.Pooling)
+ {
+ Connector = RequestPooledConnector(Connection);
+ }
+ else
+ {
+ Connector = GetNonPooledConnector(Connection);
+ }
+
+ return Connector;
}
/// <summary>
- /// Inserts a connector at the head of a shared connector list.
+ /// Find a pooled connector. Handle locking and timeout here.
/// </summary>
- /// <param name="Connector">The connctor to be inserted</param>
- internal void InsertSharedConnector( Npgsql.Connector Connector )
+ private NpgsqlConnector RequestPooledConnector (NpgsqlConnection Connection)
{
- if ( this.SharedConnectors == null ) // the list is empty
+ NpgsqlConnector Connector;
+ Int32 timeoutMilliseconds = Connection.Timeout * 1000;
+
+ lock(this)
{
- // make the connector the only member
- Connector.Prev = Connector.Next = null;
+ Connector = RequestPooledConnectorInternal(Connection);
}
- else // the list is not empty
+
+ while (Connector == null && timeoutMilliseconds > 0)
{
- // Make the connector the new list head
- Connector.Next = this.SharedConnectors;
- this.SharedConnectors.Prev = Connector;
- Connector.Prev = null;
+ Int32 ST = timeoutMilliseconds > 1000 ? 1000 : timeoutMilliseconds;
+
+ Thread.Sleep(ST);
+ timeoutMilliseconds -= ST;
+
+ lock(this)
+ {
+ Connector = RequestPooledConnectorInternal(Connection);
+ }
}
- // point the list to the new head
- this.SharedConnectors = Connector;
+
+ if (Connector == null)
+ {
+ if (Connection.Timeout > 0)
+ {
+ throw new Exception("Timeout while getting a connection from pool.");
+ }
+ else
+ {
+ throw new Exception("Connection pool exceeds maximum size.");
+ }
+ }
+
+ return Connector;
}
/// <summary>
- /// Inserts a connector at the head of a pooled connector list.
+ /// Find a pooled connector. Handle shared/non-shared here.
/// </summary>
- /// <param name="Connector">The connctor to be inserted</param>
- internal void InsertPooledConnector( Npgsql.Connector Connector )
+ private NpgsqlConnector RequestPooledConnectorInternal (NpgsqlConnection Connection)
{
- if ( this.PooledConnectors == null ) // the list is empty
+ NpgsqlConnector Connector = null;
+ Boolean Shared = false;
+
+ // If sharing were implemented, I suppose Shared would be set based
+ // on some property on the Connection.
+
+ if (! Shared)
{
- // make the connector the only member
- Connector.Prev = Connector.Next = null;
+ Connector = GetPooledConnector(Connection);
}
- else // the list is not empty
+ else
{
- // Make the connector the new list head
- Connector.Next = this.PooledConnectors;
- this.PooledConnectors.Prev = Connector;
- Connector.Prev = null;
+ // Connection sharing? What's that?
+ throw new NotImplementedException("Internal: Shared pooling not implemented");
}
- // point the list to the new head
- this.PooledConnectors = Connector;
+
+ return Connector;
}
/// <summary>
- /// Searches the shared and pooled connector lists for a
- /// matching connector object or creates a new one.
+ /// Releases a connector, possibly back to the pool for future use.
/// </summary>
- /// <param name="ConnectString">used to connect to the
- /// database server</param>
- /// <param name="Shared">Allows multiple connections
- /// on a single connector. </param>
- /// <returns>A pooled connector object.</returns>
- internal Npgsql.Connector RequestConnector ( string ConnectString,
- bool Shared )
+ /// <remarks>
+ /// Pooled connectors will be put back into the pool if there is room.
+ /// Shared connectors should just have their use count decremented
+ /// since they always stay in the shared pool.
+ /// </remarks>
+ /// <param name="Connector">The connector to release.</param>
+ /// <param name="ForceClose">Force the connector to close, even if it is pooled.</param>
+ public void ReleaseConnector (NpgsqlConnection Connection, NpgsqlConnector Connector)
{
- Npgsql.Connector Connector;
+ if (Connector.Pooled)
+ {
+ ReleasePooledConnector(Connection, Connector);
+ }
+ else
+ {
+ UngetNonPooledConnector(Connection, Connector);
+ }
+ }
+
+ /// <summary>
+ /// Release a pooled connector. Handle locking here.
+ /// </summary>
+ private void ReleasePooledConnector (NpgsqlConnection Connection, NpgsqlConnector Connector)
+ {
+ lock(this)
+ {
+ ReleasePooledConnectorInternal(Connection, Connector);
+ }
+ }
+
+ /// <summary>
+ /// Release a pooled connector. Handle shared/non-shared here.
+ /// </summary>
+ private void ReleasePooledConnectorInternal (NpgsqlConnection Connection, NpgsqlConnector Connector)
+ {
+ if (! Connector.Shared)
+ {
+ UngetPooledConnector(Connection, Connector);
+ }
+ else
+ {
+ // Connection sharing? What's that?
+ throw new NotImplementedException("Internal: Shared pooling not implemented");
+ }
+ }
+
+ /// <summary>
+ /// Create a connector without any pooling functionality.
+ /// </summary>
+ private NpgsqlConnector GetNonPooledConnector(NpgsqlConnection Connection)
+ {
+ NpgsqlConnector Connector;
- if ( Shared )
+ Connector = CreateConnector(Connection);
+
+ Connector.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
+ Connector.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
+ Connector.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
+
+ Connector.Open();
+
+ return Connector;
+ }
+
+ /// <summary>
+ /// Find an available pooled connector in the non-shared pool, or create
+ /// a new one if none found.
+ /// </summary>
+ private NpgsqlConnector GetPooledConnector(NpgsqlConnection Connection)
+ {
+ ConnectorQueue Queue;
+ NpgsqlConnector Connector = null;
+
+ // Try to find a queue.
+ Queue = (ConnectorQueue)PooledConnectors[Connection.ConnectionString.ToString()];
+
+ if (Queue == null)
{
- // if a shared connector is requested then the
- // Shared Connector List is searched first
+ Queue = new ConnectorQueue();
+ PooledConnectors[Connection.ConnectionString.ToString()] = Queue;
+ }
+
+ if (Queue.Count > 0)
+ {
+ // Found a queue with connectors. Grab the top one.
+
+ // Check if the connector is still valid.
- for ( Connector = Npgsql.ConnectorPool.ConnectorPoolMgr.SharedConnectors;
- Connector != null; Connector = Connector.Next )
+ while (true)
{
- if ( Connector.ConnectString == ConnectString )
- { // Bingo!
- // Return the shared connector to caller
- Connector.mShareCount++;
- return Connector;
+ Connector = (NpgsqlConnector)Queue.Dequeue();
+ if (Connector.IsValid())
+ {
+ Queue.UseCount++;
+ break;
}
+
+ Queue.UseCount--;
+
+ if (Queue.Count <= 0)
+ return GetPooledConnector(Connection);
+
+
}
+
+
+
}
- else
+ else if (Queue.Count + Queue.UseCount < Connection.MaxPoolSize)
{
- // if a shared connector could not be found or a
- // nonshared connector is requested, then the pooled
- // (unused) connectors are beeing searched.
+ Connector = CreateConnector(Connection);
- for ( Connector = Npgsql.ConnectorPool.ConnectorPoolMgr.PooledConnectors;
- Connector != null; Connector = Connector.Next )
+ Connector.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
+ Connector.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
+ Connector.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
+
+ try
{
- if ( Connector.ConnectString == ConnectString )
- { // Bingo!
- // Remove the Connector from the pooled connectors list.
- this.CutOutConnector( Connector );
- }
- Connector.Shared = Shared;
- if ( Shared )
+ Connector.Open();
+ }
+ catch {
+ try
{
- // Shared Connectors are then put in the shared
- // connectors list in order to be used by
- // additional clients.
- this.InsertSharedConnector( Connector );
- Connector.mShareCount++;
+ Connector.Close();
+ }
+ catch {}
+
+ throw;
}
- // done...
- return Connector;
+
+
+ Queue.UseCount++;
+ }
+
+ // Meet the MinPoolSize requirement if needed.
+ if (Connection.MinPoolSize > 0)
+ {
+ while (Queue.Count + Queue.UseCount < Connection.MinPoolSize)
+ {
+ NpgsqlConnector Spare = CreateConnector(Connection);
+
+ Spare.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
+ Spare.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
+ Spare.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
+
+ Spare.Open();
+
+ Spare.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
+ Spare.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
+ Spare.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
+
+ Queue.Enqueue(Connector);
}
}
- // No suitable connector could be found, so create new one
- Connector = new Npgsql.Connector( ConnectString, Shared );
+ return Connector;
+ }
- // Shared connections are added to the shared connectors list
- if ( Shared )
+ /// <summary>
+ /// Find an available shared connector in the shared pool, or create
+ /// a new one if none found.
+ /// </summary>
+ private NpgsqlConnector GetSharedConnector(NpgsqlConnection Connection)
+ {
+ // To be implemented
+
+ return null;
+ }
+
+ private NpgsqlConnector CreateConnector(NpgsqlConnection Connection)
+ {
+ return new NpgsqlConnector(
+ Connection.ConnectionStringValues.Clone(),
+ Connection.Pooling,
+ false
+ );
+ }
+
+
+ /// <summary>
+ /// This method is only called when NpgsqlConnection.Dispose(false) is called which means a
+ /// finalization. This also means, an NpgsqlConnection was leak. We clear pool count so that
+ /// client doesn't end running out of connections from pool. When the connection is finalized, its underlying
+ /// socket is closed.
+ /// </summary
+ public void FixPoolCountBecauseOfConnectionDisposeFalse(NpgsqlConnection Connection)
+ {
+ ConnectorQueue Queue;
+
+ // Prevent multithread access to connection pool count.
+ lock(this)
{
- this.InsertSharedConnector( Connector );
- Connector.mShareCount++;
+ // Try to find a queue.
+ Queue = (ConnectorQueue)PooledConnectors[Connection.ConnectionString.ToString()];
+
+ if (Queue != null)
+ Queue.UseCount--;
+
}
+ }
- // and then returned to the caller
- return Connector;
+ /// <summary>
+ /// Close the connector.
+ /// </summary>
+ /// <param name="Connector">Connector to release</param>
+ private void UngetNonPooledConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)
+ {
+ Connector.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
+ Connector.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
+ Connector.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
+
+ if (Connector.Transaction != null)
+ {
+ Connector.Transaction.Cancel();
+ }
+
+ Connector.Close();
+ }
+
+ /// <summary>
+ /// Put a pooled connector into the pool queue.
+ /// </summary>
+ /// <param name="Connector">Connector to pool</param>
+ private void UngetPooledConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)
+ {
+ ConnectorQueue Queue;
+
+ // Find the queue.
+ Queue = (ConnectorQueue)PooledConnectors[Connector.ConnectionString.ToString()];
+
+ if (Queue == null)
+ {
+ throw new InvalidOperationException("Internal: No connector queue found for existing connector.");
+ }
+
+ Connector.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
+ Connector.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
+ Connector.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
+
+ Queue.UseCount--;
+
+ if (! Connector.IsInitialized)
+ {
+ if (Connector.Transaction != null)
+ {
+ Connector.Transaction.Cancel();
+ }
+
+ Connector.Close();
+ }
+ else
+ {
+ if (Connector.Transaction != null)
+ {
+ try
+ {
+ Connector.Transaction.Rollback();
+ }
+ catch {
+ Connector.Close()
+ ;
+ }
+ }
+ }
+
+ if (Connector.State == System.Data.ConnectionState.Open)
+ {
+ // Release all plans and portals associated with this connector.
+ Connector.ReleasePlansPortals();
+
+ Queue.Enqueue(Connector);
+ }
+ }
+
+ /// <summary>
+ /// Stop sharing a shared connector.
+ /// </summary>
+ /// <param name="Connector">Connector to unshare</param>
+ private void UngetSharedConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)
+ {
+ // To be implemented
}
}
}