Merge pull request #347 from JamesB7/master
[mono.git] / mcs / class / Mono.Data.Tds / Mono.Data.Tds.Protocol / TdsConnectionPool.cs
index 0752c5d1cfad2cf2e0d26f52b94d1ea9a79348a8..4d43d7ec399d273c8b5fbd3aa4f1ce90fd78f01e 100644 (file)
@@ -3,9 +3,11 @@
 //
 // Author:
 //   Lluis Sanchez Gual (lluis@ximian.com)
+//   Christian Hergert (christian.hergert@gmail.com)
+//   Gonzalo Paniagua Javier (gonzalo@novell.com)
 //
 // Copyright (C) 2004 Novell, Inc.
-//
+// Copyright (C) 2009 Novell, Inc.
 
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
-using Mono.Data.Tds.Protocol;
 using System;
 using System.Collections;
+using System.Text;
 using System.Threading;
 
 namespace Mono.Data.Tds.Protocol 
 {
        public class TdsConnectionPoolManager
        {
-               Hashtable pools = new Hashtable ();
+               Hashtable pools = Hashtable.Synchronized (new Hashtable ());
                TdsVersion version;
                
                public TdsConnectionPoolManager (TdsVersion version)
@@ -47,27 +49,22 @@ namespace Mono.Data.Tds.Protocol
                
                public TdsConnectionPool GetConnectionPool (string connectionString, TdsConnectionInfo info)
                {
-                       lock (pools)
-                       {
-                               TdsConnectionPool pool = (TdsConnectionPool) pools [connectionString];
-                               if (pool == null) {
-                                       pool = new TdsConnectionPool (this, info);
-                                       pools [connectionString] = pool;
-                               }
-                               return pool;
+                       TdsConnectionPool pool = (TdsConnectionPool) pools [connectionString];
+                       if (pool == null) {
+                               pools [connectionString] = new TdsConnectionPool (this, info);
+                               pool = (TdsConnectionPool) pools [connectionString];
                        }
+                       return pool;
                }
-               
-               public Hashtable GetConnectionPool ()
+
+               public TdsConnectionPool GetConnectionPool (string connectionString)
                {
-                       lock (pools)
-                       {
-                               return pools;
-                       }
+                       return (TdsConnectionPool) pools [connectionString];
                }
-               
+
                public virtual Tds CreateConnection (TdsConnectionInfo info)
                {
+                       //Console.WriteLine ("CreateConnection: TdsVersion:{0}", version);
                        switch (version)
                        {
                                case TdsVersion.tds42:
@@ -81,6 +78,11 @@ namespace Mono.Data.Tds.Protocol
                        }
                        throw new NotSupportedException ();
                }
+
+               public IDictionary GetConnectionPool ()
+               {
+                       return pools;
+               }
        }
        
        public class TdsConnectionInfo
@@ -101,148 +103,181 @@ namespace Mono.Data.Tds.Protocol
                public int Timeout;
                public int PoolMinSize;
                public int PoolMaxSize;
+
+               public override string ToString ()
+               {
+                       StringBuilder sb = new StringBuilder ();
+                       sb.AppendFormat ("DataSouce: {0}\n", DataSource);
+                       sb.AppendFormat ("Port: {0}\n", Port);
+                       sb.AppendFormat ("PacketSize: {0}\n", PacketSize);
+                       sb.AppendFormat ("Timeout: {0}\n", Timeout);
+                       sb.AppendFormat ("PoolMinSize: {0}\n", PoolMinSize);
+                       sb.AppendFormat ("PoolMaxSize: {0}", PoolMaxSize);
+                       return sb.ToString ();
+               }
        }
-       
+
        public class TdsConnectionPool
        {
-               ArrayList list = new ArrayList ();
                TdsConnectionInfo info;
-               bool initialized;
-               bool pooling = true;
-               int activeConnections = 0;
+               bool no_pooling;
                TdsConnectionPoolManager manager;
-
+               Queue available;
+               ArrayList conns;
+               
                public TdsConnectionPool (TdsConnectionPoolManager manager, TdsConnectionInfo info)
                {
                        this.info = info;
                        this.manager = manager;
+                       conns = new ArrayList (info.PoolMaxSize);
+                       available = new Queue (info.PoolMaxSize);
+                       InitializePool ();
+               }
+
+               void InitializePool ()
+               {
+                       /* conns.Count might not be 0 when we are resetting the connection pool */
+                       for (int i = conns.Count; i < info.PoolMinSize; i++) {
+                               try {
+                                       Tds t = manager.CreateConnection (info);
+                                       conns.Add (t);
+                                       available.Enqueue (t);
+                               } catch {
+                                       // Ignore. GetConnection will throw again.
+                               }
+                       }
                }
 
                public bool Pooling {
-                       get { return pooling; }
-                       set { pooling = value; }
+                       get { return !no_pooling; }
+                       set { no_pooling = !value; }
                }
 
                #region Methods
 
+               int in_progress;
                public Tds GetConnection ()
                {
-                       Tds connection = null;
-                       lock (list)
-                       {
-                               if (!initialized) 
-                               {
-                                       for (int n = 0; n < info.PoolMinSize; n++)
-                                               list.Add (CreateConnection ());
-                                       initialized = true;
-                               }
-                               do {
-                                       if (list.Count > 0)
-                                       {
-                                               // There are available connections
-                                               connection = (Tds) list [list.Count - 1];
-                                               list.RemoveAt (list.Count - 1);
-                                               if (!connection.Reset ()) {
-                                                       try {
-                                                               connection.Disconnect ();
-                                                       } catch {}
-                                                       connection = null;
+                       if (no_pooling)
+                               return manager.CreateConnection (info);
+
+                       Tds result = null;
+                       bool create_new;
+                       int retries = info.PoolMaxSize * 2;
+retry:
+                       while (result == null) {
+                               create_new = false;
+                               lock (available) {
+                                       if (available.Count > 0) {
+                                               result = (Tds) available.Dequeue ();
+                                               break; // .. and do the reset out of the loop
+                                       }
+                                       Monitor.Enter (conns);
+                                       try {
+                                               if (conns.Count >= info.PoolMaxSize - in_progress) {
+                                                       Monitor.Exit (conns);
+                                                       bool got_lock = Monitor.Wait (available, info.Timeout * 1000);
+                                                       if (!got_lock) {
+                                                               throw new InvalidOperationException (
+                                                                       "Timeout expired. The timeout period elapsed before a " +
+                                                                       "connection could be obtained. A possible explanation " +
+                                                                       "is that all the connections in the pool are in use, " +
+                                                                       "and the maximum pool size is reached.");
+                                                       } else if (available.Count > 0) {
+                                                               result = (Tds) available.Dequeue ();
+                                                               break; // .. and do the reset out of the loop
+                                                       }
                                                        continue;
+                                               } else {
+                                                       create_new = true;
+                                                       in_progress++;
                                                }
+                                       } finally {
+                                               Monitor.Exit (conns); // Exiting if not owned is ok < 2.x
                                        }
-
-                                       if (connection == null && activeConnections < info.PoolMaxSize)
-                                       {
-                                               // No connections available, but the connection limit
-                                               // has not been reached yet, so a new one can be created
-                                               connection = CreateConnection();
-                                       }
-
-                                       // No available connections in the pool
-                                       // Wait for somewone to release one.
-                                       if (connection == null)
-                                       {
-                                               Monitor.Wait (list);
+                               }
+                               if (create_new) {
+                                       try {
+                                               result = manager.CreateConnection (info);
+                                               lock (conns)
+                                                       conns.Add (result);
+                                               return result;
+                                       } finally {
+                                               lock (available)
+                                                       in_progress--;
                                        }
-                               } 
-                               while (connection == null);
+                               }
                        }
 
-                       return connection;
+                       bool remove_cnc = true;
+                       Exception exc = null;
+                       try {
+                               remove_cnc = (!result.IsConnected || !result.Reset ());
+                       } catch (Exception e) {
+                               remove_cnc = true;
+                               exc = e;
+                       }
+                       if (remove_cnc) {
+                               lock (conns)
+                                       conns.Remove (result);
+                               result.Disconnect ();
+                               retries--;
+                               if (retries == 0)
+                                       throw exc;
+                               result = null;
+                               goto retry;
+                       }
+                       return result;
                }
 
-               public void ReleaseConnection (Tds tds)
+               public void ReleaseConnection (Tds connection)
                {
-                       lock (list)
-                       {
-                               list.Add (tds);
-                               Monitor.Pulse (list);
+                       if (connection == null)
+                               return;
+                       if (no_pooling) {
+                               connection.Disconnect ();
+                               return;
                        }
-               }
 
-#if NET_2_0
-               public void ReleaseConnection (ref Tds tds)
-               {
-                       lock (list)
-                       {
-                               if (pooling == false) {
-                                       try {
-                                               tds.Disconnect ();
-                                       } catch {}
-                                       tds = null;
-                               } else {
-                                       list.Add (tds);
-                                       Monitor.Pulse (list);
-                               }
+                       if (connection.poolStatus == 2) {
+                               lock (conns)
+                                       conns.Remove (connection);
+                               connection.Disconnect ();
+                               connection = null;
+                       }
+                       lock (available) {
+                               if (connection != null) // connection is still open
+                                       available.Enqueue (connection);
+                               // We pulse even if we don't queue, because null means that there's a slot
+                               // available in 'conns'
+                               Monitor.Pulse (available);
                        }
                }
 
+#if NET_2_0
                public void ResetConnectionPool ()
                {
-                       lock (list)
-                       {
-                               Tds connection = null;
-                               while (list.Count > 0) {
-                                       // There are available connections
-                                       connection = (Tds) list [list.Count - 1];
-                                       list.RemoveAt (list.Count - 1);
-                                       if (!connection.Reset ()) {
-                                               try {
-                                                       connection.Disconnect ();
-                                               } catch {}
-                                               connection = null;
+                       lock (available) {
+                               lock (conns) {
+                                       Tds tds;
+                                       int i;
+                                       for (i = conns.Count - 1; i >= 0; i--) {
+                                               tds = (Tds) conns [i];
+                                               tds.poolStatus = 2; // 2 -> disconnect me upon release
                                        }
-                               }
-                       }
-               }
-
-               public void ResetConnectionPool (Tds connection)
-               {
-                       lock (list)
-                       {
-                               if (list.Count > 0) {
-                                       // There are available connections
-                                       int index = list.IndexOf (connection);
-                                       if (index != -1) {
-                                               list.RemoveAt (index);
-                                               if (!connection.Reset ()) {
-                                                       try {
-                                                               connection.Disconnect ();
-                                                       } catch {}
-                                                       connection = null;
-                                               }
+                                       for (i = available.Count - 1; i >= 0; i--) {
+                                               tds = (Tds) available.Dequeue ();
+                                               tds.Disconnect ();
+                                               conns.Remove (tds);
                                        }
+                                       available.Clear ();
+                                       InitializePool ();
                                }
+                               Monitor.PulseAll (available);
                        }
                }
 #endif
-               
-               Tds CreateConnection ()
-               {
-                       activeConnections++;
-                       return manager.CreateConnection (info);
-               }
-               
                #endregion // Methods
        }
 }
+