// Copyright (C) 2004-2007 MySQL AB // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as published by // the Free Software Foundation // // There are special exceptions to the terms and conditions of the GPL // as it is applied to this software. View the full text of the // exception in file EXCEPTIONS in the directory of this software // distribution. // // 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 description for MySqlPool. /// internal sealed class MySqlPool { #if NET20 private List inUsePool; private Queue idlePool; #else private ArrayList inUsePool; private Queue idlePool; #endif private MySqlConnectionStringBuilder settings; private uint minSize; private uint maxSize; private ProcedureCache procedureCache; private bool beingCleared; private int available; private AutoResetEvent autoEvent; 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; #if NET20 inUsePool = new List((int)maxSize); idlePool = new Queue((int)maxSize); #else inUsePool =new ArrayList((int)maxSize); idlePool = new Queue((int)maxSize); #endif // prepopulate the idle pool to minSize for (int i = 0; i < minSize; i++) idlePool.Enqueue(CreateNewPooledConnection()); procedureCache = new ProcedureCache((int)settings.ProcedureCacheSize); beingCleared = false; } #region Properties public MySqlConnectionStringBuilder Settings { get { return settings; } set { settings = value; } } public ProcedureCache ProcedureCache { get { return procedureCache; } } /// /// It is assumed that this property will only be used from inside an active /// lock. /// private bool HasIdleConnections { get { return idlePool.Count > 0; } } private int NumConnections { get { return idlePool.Count + inUsePool.Count; } } /// /// Indicates whether this pool is being cleared. /// public bool BeingCleared { get { return beingCleared; } } #endregion /// /// CheckoutConnection handles the process of pulling a driver /// from the idle pool, possibly resetting its state, /// and adding it to the in use pool. We assume that this method is only /// called inside an active lock so there is no need to acquire a new lock. /// /// An idle driver object private Driver CheckoutConnection() { Driver driver = (Driver)idlePool.Dequeue(); return driver; } private void CheckConn(Driver driver) { // first check to see that the server is still alive if (!driver.Ping()) { driver.Close(); driver = CreateNewPooledConnection(); } // if the user asks us to ping/reset pooled connections // do so now if (settings.ConnectionReset) driver.Reset(); } /// /// It is assumed that this method is only called from inside an active lock. /// 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. bool isIdle; lock ((idlePool as ICollection).SyncRoot) { if (!HasIdleConnections) { driver = CreateNewPooledConnection(); isIdle = false; } else { driver = CheckoutConnection(); isIdle = true; } } if (isIdle) CheckConn(driver); Debug.Assert(driver != null); lock ((inUsePool as ICollection).SyncRoot) { inUsePool.Add(driver); } return driver; } /// /// It is assumed that this method is only called from inside an active lock. /// private Driver CreateNewPooledConnection() { Debug.Assert((maxSize - NumConnections) > 0, "Pool out of sync."); 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.IsTooOld() || beingCleared) { driver.Close(); Debug.Assert(!idlePool.Contains(driver)); } else { lock ((idlePool as ICollection).SyncRoot) { idlePool.Enqueue(driver); } } Interlocked.Increment(ref available); autoEvent.Set(); } /// /// 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. /// /// 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() { int count = Interlocked.Decrement(ref available); if (count < 0) { Interlocked.Increment(ref available); return null; } try { Driver driver = GetPooledConnection(); return driver; } catch (Exception ex) { if (settings.Logging) Logger.LogException(ex); Interlocked.Increment(ref available); throw; } } public Driver GetConnection() { int fullTimeOut = (int)settings.ConnectionTimeout * 1000; int timeOut = fullTimeOut; DateTime start = DateTime.Now; while (timeOut > 0) { 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); } /// /// Clears this pool of all idle connections and marks this pool and being cleared /// so all other connections are closed when they are returned. /// 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) { 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. } } } }