context: // Copyright ?2004, 2011, Oracle and/or its affiliates. All rights reserved.
  //
  // MySQL Connector/NET is licensed under the terms of the GPLv2
  // <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most 
  // MySQL Connectors. There are special exceptions to the terms and 
  // conditions of the GPLv2 as it is applied to this software, see the 
  // FLOSS License Exception
  // <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  //
  // This program is free software; you can redistribute it and/or modify 
  // it under the terms of the GNU General Public License as published 
  // by the Free Software Foundation; version 2 of the License.
  //
  // This program 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 General Public License 
  // for more details.
  //
  // You should have received a copy of the GNU General Public License along 
  // with this program; if not, write to the Free Software Foundation, Inc., 
  // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  
  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Threading;
  using MySql.Data.MySqlClient.Properties;
  
  namespace MySql.Data.MySqlClient
  {
    /// <summary>
    /// Summary description for MySqlPool.
    /// </summary>
    internal sealed class MySqlPool
    {
      private List<Driver> inUsePool;
      private Queue<Driver> idlePool;
      private MySqlConnectionStringBuilder settings;
      private uint minSize;
      private uint maxSize;
      private ProcedureCache procedureCache;
      private bool beingCleared;
      private int available;
      private AutoResetEvent autoEvent;
  
      private void EnqueueIdle(Driver driver)
      {
        driver.IdleSince = DateTime.Now;
        idlePool.Enqueue(driver);
      }
      public MySqlPool(MySqlConnectionStringBuilder settings)
      {
        minSize = settings.MinimumPoolSize;
        maxSize = settings.MaximumPoolSize;
  
        available = (int)maxSize;
        autoEvent = new AutoResetEvent(false);
  
        if (minSize > maxSize)
          minSize = maxSize;
        this.settings = settings;
        inUsePool = new List<Driver>((int)maxSize);
        idlePool = new Queue<Driver>((int)maxSize);
  
        // prepopulate the idle pool to minSize
        for (var i = 0; i < minSize; i++)
          EnqueueIdle(CreateNewPooledConnection());
  
        procedureCache = new ProcedureCache((int)settings.ProcedureCacheSize);
      }
  
      #region Properties
  
      public MySqlConnectionStringBuilder Settings
      {
        get { return settings; }
        set { settings = value; }
      }
  
      public ProcedureCache ProcedureCache
      {
        get { return procedureCache; }
      }
  
      /// <summary>
      /// It is assumed that this property will only be used from inside an active
      /// lock.
      /// </summary>
      private bool HasIdleConnections
      {
        get { return idlePool.Count > 0; }
      }
  
      private int NumConnections
      {
        get { return idlePool.Count + inUsePool.Count; }
      }
  
      /// <summary>
      /// Indicates whether this pool is being cleared.
      /// </summary>
      public bool BeingCleared
      {
        get { return beingCleared; }
      }
  
      internal Hashtable ServerProperties { get; set; }
  
      #endregion
  
      /// <summary>
      /// It is assumed that this method is only called from inside an active lock.
      /// </summary>
      private Driver GetPooledConnection()
      {
        Driver driver = null;
  
        // if we don't have an idle connection but we have room for a new
        // one, then create it here.
        lock ((idlePool as ICollection).SyncRoot)
        {
          if (HasIdleConnections)
            driver = idlePool.Dequeue();
        }
  
        // Obey the connection timeout
        if (driver != null)
        {
          try
          {
            driver.ResetTimeout((int)Settings.ConnectionTimeout * 1000);
          }
          catch (Exception)
          {
            driver.Close();
            driver = null;
          }
        }
  
        if (driver != null)
        {
          // first check to see that the server is still alive
          if (!driver.Ping())
          {
            driver.Close();
            driver = null;
          }
          else if (settings.ConnectionReset)
            // if the user asks us to ping/reset pooled connections
            // do so now
            driver.Reset();
        }
        if (driver == null)
          driver = CreateNewPooledConnection();
  
        Debug.Assert(driver != null);
        lock ((inUsePool as ICollection).SyncRoot)
        {
          inUsePool.Add(driver);
        }
        return driver;
      }
  
      /// <summary>
      /// It is assumed that this method is only called from inside an active lock.
      /// </summary>
      private Driver CreateNewPooledConnection()
      {
        Debug.Assert((maxSize - NumConnections) > 0, "Pool out of sync.");
  
  //      var driver = Driver.Create(settings);
        Driver driver = Driver.Create(settings);
        driver.Pool = this;
        return driver;
      }
  
      public void ReleaseConnection(Driver driver)
      {
        lock ((inUsePool as ICollection).SyncRoot)
        {
          if (inUsePool.Contains(driver))
            inUsePool.Remove(driver);
        }
  
        if (driver.ConnectionLifetimeExpired() || beingCleared)
        {
          driver.Close();
          Debug.Assert(!idlePool.Contains(driver));
        }
        else
        {
          lock ((idlePool as ICollection).SyncRoot)
          {
            EnqueueIdle(driver);
          }
        }
  
        Interlocked.Increment(ref available);
        autoEvent.Set();
      }
  
      /// <summary>
      /// Removes a connection from the in use pool.  The only situations where this method 
      /// would be called are when a connection that is in use gets some type of fatal exception
      /// or when the connection is being returned to the pool and it's too old to be 
      /// returned.
      /// </summary>
      /// <param name="driver"></param>
      public void RemoveConnection(Driver driver)
      {
        lock ((inUsePool as ICollection).SyncRoot)
        {
          if (inUsePool.Contains(driver))
          {
            inUsePool.Remove(driver);
            Interlocked.Increment(ref available);
            autoEvent.Set();
          }
        }
  
        // if we are being cleared and we are out of connections then have
        // the manager destroy us.
        if (beingCleared && NumConnections == 0)
          MySqlPoolManager.RemoveClearedPool(this);
      }
  
      private Driver TryToGetDriver()
      {
  //      var count = Interlocked.Decrement(ref available);
          int count = Interlocked.Decrement(ref available);
        if (count < 0)
        {
          Interlocked.Increment(ref available);
          return null;
        }
        try
        {
  //        var driver = GetPooledConnection();
            Driver driver = GetPooledConnection();
          return driver;
        }
        catch (Exception ex)
        {
          MySqlTrace.LogError(-1, ex.Message);
          Interlocked.Increment(ref available);
          throw;
        }
      }
  
      public Driver GetConnection()
      {
        var fullTimeOut = (int)settings.ConnectionTimeout * 1000;
        var timeOut = fullTimeOut;
  
        var start = DateTime.Now;
  
        while (timeOut > 0)
        {
  //        var driver = TryToGetDriver();
            Driver driver = TryToGetDriver();
          if (driver != null) return driver;
  
          // We have no tickets right now, lets wait for one.
          if (!autoEvent.WaitOne(timeOut, false)) break;
          timeOut = fullTimeOut - (int)DateTime.Now.Subtract(start).TotalMilliseconds;
        }
        throw new MySqlException(Resources.TimeoutGettingConnection);
      }
  
      /// <summary>
      /// Clears this pool of all idle connections and marks this pool and being cleared
      /// so all other connections are closed when they are returned.
      /// </summary>
      internal void Clear()
      {
        lock ((idlePool as ICollection).SyncRoot)
        {
          // first, mark ourselves as being cleared
          beingCleared = true;
  
          // then we remove all connections sitting in the idle pool
          while (idlePool.Count > 0)
          {
  //          var d = idlePool.Dequeue();
              Driver d = idlePool.Dequeue();
            d.Close();
          }
  
          // there is nothing left to do here.  Now we just wait for all
          // in use connections to be returned to the pool.  When they are
          // they will be closed.  When the last one is closed, the pool will
          // be destroyed.
        }
      }
  
      /// <summary>
      /// Remove expired drivers from the idle pool
      /// </summary>
      /// <returns></returns>
      /// <remarks>
      /// Closing driver is a potentially lengthy operation involving network
      /// IO. Therefore we do not close expired drivers while holding 
      /// idlePool.SyncRoot lock. We just remove the old drivers from the idle
      /// queue and return them to the caller. The caller will need to close 
      /// them (or let GC close them)
      /// </remarks>
      internal List<Driver> RemoveOldIdleConnections()
      {
  //      var oldDrivers = new List<Driver>();
  //      var now = DateTime.Now;
          List<Driver> oldDrivers = new List<Driver>();
          DateTime now = DateTime.Now;
  
        lock ((idlePool as ICollection).SyncRoot)
        {
          // The drivers appear to be ordered by their age, i.e it is
          // sufficient to remove them until the first element is not
          // too old.
          while (idlePool.Count > minSize)
          {
  //          var d = idlePool.Peek();
  //          var expirationTime = d.IdleSince.Add(
              Driver d = idlePool.Peek();
              DateTime expirationTime = d.IdleSince.Add(
              new TimeSpan(0, 0, MySqlPoolManager.maxConnectionIdleTime));
  
gt:           if (expirationTime.CompareTo(now) < 0)