Merge pull request #1163 from AerisG222/more_mvc_fixes
[mono.git] / mcs / class / Mono.Data.Tds / Mono.Data.Tds.Protocol / TdsConnectionPool.cs
index 49b1f9ba2dba919043306a3349eefc192168c9b7..7ec2fe0e9e27586fefa3a9613f558027fcf36a7a 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,19 +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 virtual ITds CreateConnection (TdsConnectionInfo info)
+
+               public TdsConnectionPool GetConnectionPool (string connectionString)
                {
+                       return (TdsConnectionPool) pools [connectionString];
+               }
+
+               public virtual Tds CreateConnection (TdsConnectionInfo info)
+               {
+                       //Console.WriteLine ("CreateConnection: TdsVersion:{0}", version);
                        switch (version)
                        {
                                case TdsVersion.tds42:
@@ -67,17 +72,28 @@ namespace Mono.Data.Tds.Protocol
                                case TdsVersion.tds50:
                                        return new Tds50 (info.DataSource, info.Port, info.PacketSize, info.Timeout);
                                case TdsVersion.tds70:
-                                       return new Tds70 (info.DataSource, info.Port, info.PacketSize, info.Timeout);
+                                       return new Tds70 (info.DataSource, info.Port, info.PacketSize, info.Timeout, info.LifeTime);
                                case TdsVersion.tds80:
-                                       return new Tds80 (info.DataSource, info.Port, info.PacketSize, info.Timeout);
+                                       return new Tds80 (info.DataSource, info.Port, info.PacketSize, info.Timeout, info.LifeTime);
                        }
                        throw new NotSupportedException ();
                }
+
+               public IDictionary GetConnectionPool ()
+               {
+                       return pools;
+               }
        }
        
        public class TdsConnectionInfo
        {
+               [Obsolete ("Use the constructor that receives a lifetime parameter")]
                public TdsConnectionInfo (string dataSource, int port, int packetSize, int timeout, int minSize, int maxSize)
+                       : this (dataSource, port, packetSize, timeout, minSize, maxSize, 0)
+               {
+               }
+
+               public TdsConnectionInfo (string dataSource, int port, int packetSize, int timeout, int minSize, int maxSize, int lifeTime)
                {
                        DataSource = dataSource;
                        Port = port;
@@ -85,94 +101,191 @@ namespace Mono.Data.Tds.Protocol
                        Timeout = timeout;
                        PoolMinSize = minSize;
                        PoolMaxSize = maxSize;
+                       LifeTime = lifeTime;
                }
                
                public string DataSource;
                public int Port;
                public int PacketSize;
                public int Timeout;
+               public int LifeTime;
                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;
-               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 !no_pooling; }
+                       set { no_pooling = !value; }
                }
 
                #region Methods
 
-               public ITds GetConnection ()
+               int in_progress;
+               public Tds GetConnection ()
                {
-                       ITds 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 = (ITds) 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 (ITds 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 (connection.poolStatus == 2 || connection.Expired) {
+                               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);
                        }
                }
-               
-               ITds CreateConnection ()
+
+#if NET_2_0
+               public void ResetConnectionPool ()
                {
-                       activeConnections++;
-                       return manager.CreateConnection (info);
+                       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
        }
 }
+