-
+// Copyright (C) 2002 The Npgsql Development Team
+// npgsql-general@gborg.postgresql.org
+// http://gborg.postgresql.org/project/npgsql/projdisplay.php
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
// ConnectorPool.cs
// ------------------------------------------------------------------
// 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
- {
- /// <value>Unique static instance of the connector pool
- /// mamager.</value>
- internal static ConnectorPool ConnectorPoolMgr = new Npgsql.ConnectorPool();
-
- /// <value>List of 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;
-
- /// <value>List of used, shared conncetors.</value>
- /// <remarks>Points to the head of a double linked list</remarks>
- private Npgsql.Connector SharedConnectors;
-
- /// <summary>
- /// Cuts out a connector from the the list it is in.
- /// </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 )
- {
- if ( Connector.Prev != null ) Connector.Prev.Next = Connector.Next;
- if ( Connector.Next != null ) Connector.Next.Prev = Connector.Prev;
- }
-
- /// <summary>
- /// Inserts a connector at the head of a shared connector list.
- /// </summary>
- /// <param name="Connector">The connctor to be inserted</param>
- internal void InsertSharedConnector( Npgsql.Connector Connector )
- {
- if ( this.SharedConnectors == null ) // the list is empty
- {
- // make the connector the only member
- Connector.Prev = Connector.Next = null;
- }
- else // the list is not empty
- {
- // Make the connector the new list head
- Connector.Next = this.SharedConnectors;
- this.SharedConnectors.Prev = Connector;
- Connector.Prev = null;
- }
- // point the list to the new head
- this.SharedConnectors = Connector;
- }
-
- /// <summary>
- /// Inserts a connector at the head of a pooled connector list.
- /// </summary>
- /// <param name="Connector">The connctor to be inserted</param>
- internal void InsertPooledConnector( Npgsql.Connector Connector )
- {
- if ( this.PooledConnectors == null ) // the list is empty
- {
- // make the connector the only member
- Connector.Prev = Connector.Next = null;
- }
- else // the list is not empty
- {
- // Make the connector the new list head
- Connector.Next = this.PooledConnectors;
- this.PooledConnectors.Prev = Connector;
- Connector.Prev = null;
- }
- // point the list to the new head
- this.PooledConnectors = Connector;
- }
-
- /// <summary>
- /// Searches the shared and pooled connector lists for a
- /// matching connector object or creates a new one.
- /// </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 )
- {
- Npgsql.Connector Connector;
-
- if ( Shared )
- {
- // if a shared connector is requested then the
- // Shared Connector List is searched first
-
- for ( Connector = Npgsql.ConnectorPool.ConnectorPoolMgr.SharedConnectors;
- Connector != null; Connector = Connector.Next )
- {
- if ( Connector.ConnectString == ConnectString )
- { // Bingo!
- // Return the shared connector to caller
- Connector.mShareCount++;
- return Connector;
- }
- }
- }
- else
- {
- // if a shared connector could not be found or a
- // nonshared connector is requested, then the pooled
- // (unused) connectors are beeing searched.
-
- for ( Connector = Npgsql.ConnectorPool.ConnectorPoolMgr.PooledConnectors;
- Connector != null; Connector = Connector.Next )
- {
- if ( Connector.ConnectString == ConnectString )
- { // Bingo!
- // Remove the Connector from the pooled connectors list.
- this.CutOutConnector( Connector );
- }
- Connector.Shared = Shared;
- if ( Shared )
- {
- // Shared Connectors are then put in the shared
- // connectors list in order to be used by
- // additional clients.
- this.InsertSharedConnector( Connector );
- Connector.mShareCount++;
- }
- // done...
- return Connector;
- }
- }
-
- // No suitable connector could be found, so create new one
- Connector = new Npgsql.Connector( ConnectString, Shared );
-
- // Shared connections are added to the shared connectors list
- if ( Shared )
- {
- this.InsertSharedConnector( Connector );
- Connector.mShareCount++;
- }
-
- // and then returned to the caller
- return Connector;
- }
- }
+ /// <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 NpgsqlConnectorPool ConnectorPoolMgr = new NpgsqlConnectorPool();
+
+ public NpgsqlConnectorPool()
+ {
+ PooledConnectors = new Hashtable();
+ }
+
+
+ /// <value>Map of index to unused pooled connectors, avaliable to the
+ /// next RequestConnector() call.</value>
+ /// <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>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>
+ /// Searches the shared and pooled connector lists for a
+ /// matching connector object or creates a new one.
+ /// </summary>
+ /// <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)
+ {
+ NpgsqlConnector Connector;
+
+ if (Connection.Pooling)
+ {
+ Connector = RequestPooledConnector(Connection);
+ }
+ else
+ {
+ Connector = GetNonPooledConnector(Connection);
+ }
+
+ return Connector;
+ }
+
+ /// <summary>
+ /// Find a pooled connector. Handle locking and timeout here.
+ /// </summary>
+ private NpgsqlConnector RequestPooledConnector (NpgsqlConnection Connection)
+ {
+ NpgsqlConnector Connector;
+ Int32 timeoutMilliseconds = Connection.Timeout * 1000;
+
+ lock(this)
+ {
+ Connector = RequestPooledConnectorInternal(Connection);
+ }
+
+ while (Connector == null && timeoutMilliseconds > 0)
+ {
+ Int32 ST = timeoutMilliseconds > 1000 ? 1000 : timeoutMilliseconds;
+
+ Thread.Sleep(ST);
+ timeoutMilliseconds -= ST;
+
+ lock(this)
+ {
+ Connector = RequestPooledConnectorInternal(Connection);
+ }
+ }
+
+ 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>
+ /// Find a pooled connector. Handle shared/non-shared here.
+ /// </summary>
+ private NpgsqlConnector RequestPooledConnectorInternal (NpgsqlConnection Connection)
+ {
+ 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)
+ {
+ Connector = GetPooledConnector(Connection);
+ }
+ else
+ {
+ // Connection sharing? What's that?
+ throw new NotImplementedException("Internal: Shared pooling not implemented");
+ }
+
+ return Connector;
+ }
+
+ /// <summary>
+ /// Releases a connector, possibly back to the pool for future use.
+ /// </summary>
+ /// <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)
+ {
+ 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;
+
+ 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)
+ {
+ 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.
+
+ while (true)
+ {
+ Connector = (NpgsqlConnector)Queue.Dequeue();
+ if (Connector.IsValid())
+ {
+ Queue.UseCount++;
+ break;
+ }
+
+ Queue.UseCount--;
+
+ if (Queue.Count <= 0)
+ return GetPooledConnector(Connection);
+
+
+ }
+
+
+
+ }
+ else if (Queue.Count + Queue.UseCount < Connection.MaxPoolSize)
+ {
+ Connector = CreateConnector(Connection);
+
+ Connector.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
+ Connector.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
+ Connector.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
+
+ try
+ {
+ Connector.Open();
+ }
+ catch {
+ try
+ {
+ Connector.Close();
+ }
+ catch {}
+
+ throw;
+ }
+
+
+ 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);
+ }
+ }
+
+ return Connector;
+ }
+
+ /// <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)
+ {
+ // Try to find a queue.
+ Queue = (ConnectorQueue)PooledConnectors[Connection.ConnectionString.ToString()];
+
+ if (Queue != null)
+ Queue.UseCount--;
+
+ }
+ }
+
+ /// <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
+ }
+ }
}