2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mcs / class / Npgsql / Npgsql / NpgsqlConnectorPool.cs
index 7bced0fd2009efc4c012892f58728a578d866037..0783e4a9a8c54ae4c01c9ef91e0e0a16e3fcaa47 100755 (executable)
-
+//     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
+        }
+    }
 }