Merge pull request #347 from JamesB7/master
[mono.git] / mcs / class / Mono.Data.Tds / Mono.Data.Tds.Protocol / TdsConnectionPool.cs
index 11e797416aace4065d9ea94ad66bebf51366a828..4d43d7ec399d273c8b5fbd3aa4f1ce90fd78f01e 100644 (file)
@@ -4,9 +4,10 @@
 // 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
 //
 
 using System;
-#if NET_2_0
-using System.Collections.Generic;
-#else
 using System.Collections;
-#endif
+using System.Text;
 using System.Threading;
 
 namespace Mono.Data.Tds.Protocol 
 {
        public class TdsConnectionPoolManager
        {
-#if NET_2_0
-               Dictionary <string, TdsConnectionPool> pools = new Dictionary <string, TdsConnectionPool> ();
-#else
-               Hashtable pools = new Hashtable ();
-#endif
+               Hashtable pools = Hashtable.Synchronized (new Hashtable ());
                TdsVersion version;
                
                public TdsConnectionPoolManager (TdsVersion version)
@@ -55,44 +49,22 @@ namespace Mono.Data.Tds.Protocol
                
                public TdsConnectionPool GetConnectionPool (string connectionString, TdsConnectionInfo info)
                {
-                       lock (pools)
-                       {
-                               TdsConnectionPool pool = null;
-#if NET_2_0
-                               pools.TryGetValue (connectionString, out pool);
-#else
+                       TdsConnectionPool pool = (TdsConnectionPool) pools [connectionString];
+                       if (pool == null) {
+                               pools [connectionString] = new TdsConnectionPool (this, info);
                                pool = (TdsConnectionPool) pools [connectionString];
-#endif
-                               if (pool == null) {
-                                       pool = new TdsConnectionPool (this, info);
-                                       pools [connectionString] = pool;
-                               }
-                               return pool;
                        }
+                       return pool;
                }
 
                public TdsConnectionPool GetConnectionPool (string connectionString)
                {
-                       TdsConnectionPool pool = null;
-#if NET_2_0
-                       pools.TryGetValue (connectionString, out pool);
-#else
-                       pool = (TdsConnectionPool) pools [connectionString];
-#endif
-                       return pool;
+                       return (TdsConnectionPool) pools [connectionString];
                }
 
-#if NET_2_0
-               public IDictionary <string, TdsConnectionPool> GetConnectionPool ()
-#else
-               public IDictionary GetConnectionPool ()
-#endif
-               {
-                       return pools;
-               }
-               
                public virtual Tds CreateConnection (TdsConnectionInfo info)
                {
+                       //Console.WriteLine ("CreateConnection: TdsVersion:{0}", version);
                        switch (version)
                        {
                                case TdsVersion.tds42:
@@ -106,6 +78,11 @@ namespace Mono.Data.Tds.Protocol
                        }
                        throw new NotSupportedException ();
                }
+
+               public IDictionary GetConnectionPool ()
+               {
+                       return pools;
+               }
        }
        
        public class TdsConnectionInfo
@@ -126,200 +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
        {
-#if NET_2_0
-               Tds[] list;
-#else
-               object [] list;
-#endif
-
                TdsConnectionInfo info;
-               bool pooling = true;
+               bool no_pooling;
                TdsConnectionPoolManager manager;
-               ManualResetEvent connAvailable;
+               Queue available;
+               ArrayList conns;
                
                public TdsConnectionPool (TdsConnectionPoolManager manager, TdsConnectionInfo info)
                {
-                       int n = 0;
-                       
                        this.info = info;
                        this.manager = manager;
-#if NET_2_0
-                       list = new Tds[info.PoolMaxSize];
-#else
-                       list = new object [info.PoolMaxSize];
-#endif
-
-                       // Placeholder for future connections are at the beginning of the array.
-                       for (; n < info.PoolMaxSize - info.PoolMinSize; n++)
-                               list [n] = null;
+                       conns = new ArrayList (info.PoolMaxSize);
+                       available = new Queue (info.PoolMaxSize);
+                       InitializePool ();
+               }
 
-                       // Pre-populate with minimum number of connections
-                       for (; n < list.Length; n++) {
-                               list [n] = CreateConnection ();
+               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.
+                               }
                        }
-                       
-                       // Event that notifies a connection is available in the pool
-                       connAvailable = new ManualResetEvent (false);
                }
 
                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;
-                       int index;
-
-               retry:
-                       // Reset the connection available event
-                       connAvailable.Reset ();
-
-                       index = list.Length - 1;
-                       
-                       do {
-#if NET_2_0
-                               connection = list [index];
-#else
-                               connection = (Tds) list [index];
-#endif
-
-                               if (connection == null) {
-                                       // Attempt take-over of array position
-                                       connection = CreateConnection ();
-                                       (connection as Tds).poolStatus = 1;
+                       if (no_pooling)
+                               return manager.CreateConnection (info);
 
-#if NET_2_0
-                                       if (Interlocked.CompareExchange<Tds> (ref list [index], connection, null) != null) {
-#else
-                                       if (Interlocked.CompareExchange (ref list [index], connection, null) != null) {
-#endif
-                                               // Someone beat us to the punch
-                                               connection = null;
-                                       } else {
-                                               continue;
+                       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
                                        }
-                               } else {
-                                       if (Interlocked.CompareExchange (ref (connection as Tds).poolStatus, 1, 0) != 0) {
-                                               // Someone else owns this connection
-                                               connection = null;
-                                       } else {
-                                               if (!connection.Reset ()) {
-                                                       ThreadPool.QueueUserWorkItem (new WaitCallback (DestroyConnection), connection);
-                                                       // remove connection from pool
-                                                       list [index] = connection = null;
-                                                       // allow slot be re-used in same run
+                                       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 {
-                                                       continue;
+                                                       create_new = true;
+                                                       in_progress++;
                                                }
+                                       } finally {
+                                               Monitor.Exit (conns); // Exiting if not owned is ok < 2.x
                                        }
                                }
-                               
-                               index--;
-                               
-                               if (index < 0) {
-                                       // TODO: Maintain a list of indices of released connection to save some loop over
-                                       // Honor timeout - if pool is full, and no connections are available within the 
-                                       // timeout period - just throw the exception
-                                       if (info.Timeout > 0 
-                                               && !connAvailable.WaitOne (new TimeSpan (0, 0, info.Timeout), true))
-                                                       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.");
-                                       goto retry;
+                               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 connection)
                {
-                       connection.poolStatus = 0;
-                       connAvailable.Set ();
-               }
-
-#if NET_2_0
-               public void ReleaseConnection (ref Tds connection)
-               {
-                       if (pooling == false) {
-                               int index = Array.IndexOf (list, connection);
-                               ThreadPool.QueueUserWorkItem (new WaitCallback (DestroyConnection), connection);
-                               list [index] = connection = null;
-                       } else {
-                               connection.poolStatus = 0;
+                       if (connection == null)
+                               return;
+                       if (no_pooling) {
+                               connection.Disconnect ();
+                               return;
                        }
-                       connAvailable.Set ();
-               }
-
-               public void ResetConnectionPool ()
-               {
-                       Tds connection = null;
-                       int index = list.Length - 1;
 
-                       while (index >= 0)
-                       {
-                               connection = list [index];
-
-                               // skip free slots
-                               if (connection == null) {
-                                       index--;
-                                       continue;
-                               }
-
-                               if (Interlocked.CompareExchange (ref connection.poolStatus, 1, 0) == 0)
-                                       ThreadPool.QueueUserWorkItem (new WaitCallback (DestroyConnection), connection);
-                               connection.Pooling = false;
-
-                               list [index] = connection = null;
-                               connAvailable.Set ();
-
-                               index--;
+                       if (connection.poolStatus == 2) {
+                               lock (conns)
+                                       conns.Remove (connection);
+                               connection.Disconnect ();
+                               connection = null;
                        }
-               }
-
-               public void ResetConnectionPool (Tds connection)
-               {
-                       int index = Array.IndexOf (list, connection);
-
-                       if (index != -1) {
-                               if (connection != null && !connection.Reset ()) {
-                                       ThreadPool.QueueUserWorkItem (new WaitCallback (DestroyConnection), connection);
-                                       list [index] = connection = null;
-                                       connAvailable.Set ();
-                               }
+                       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);
                        }
                }
-#endif
 
-               Tds CreateConnection ()
-               {
-                       return manager.CreateConnection (info);
-               }
-               
-               void DestroyConnection (object state)
+#if NET_2_0
+               public void ResetConnectionPool ()
                {
-                       Tds connection = state as Tds;
-                       if (connection != null) {
-                               try {
-                                       connection.Disconnect ();
-                               } finally {
-                                       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
+                                       }
+                                       for (i = available.Count - 1; i >= 0; i--) {
+                                               tds = (Tds) available.Dequeue ();
+                                               tds.Disconnect ();
+                                               conns.Remove (tds);
+                                       }
+                                       available.Clear ();
+                                       InitializePool ();
                                }
+                               Monitor.PulseAll (available);
                        }
                }
-               
+#endif
                #endregion // Methods
        }
 }
+