// Copyright © 2004, 2011, Oracle and/or its affiliates. All rights reserved. // // MySQL Connector/NET is licensed under the terms of the GPLv2 // , 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 // . // // 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 description for MySqlPool. /// internal sealed class MySqlPool { private List inUsePool; private Stack idlePool; private MySqlConnectionStringBuilder settings; private uint minSize; private uint maxSize; private ProcedureCache procedureCache; private bool beingCleared; private int available; private AutoResetEvent autoEvent; internal object lockServerProperties = new object(); private void PushIdle(Driver driver) { driver.IdleSince = DateTime.Now; idlePool.Push(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((int)maxSize); idlePool = new Stack((int)maxSize); // prepopulate the idle pool to minSize for (int i = 0; i < minSize; i++) PushIdle(CreateNewPooledConnection()); procedureCache = new ProcedureCache((int)settings.ProcedureCacheSize); } #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; } } internal Hashtable ServerProperties { get; set; } internal Hashtable CharacterSets { get; set; } #endregion /// /// 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. lock ((idlePool as ICollection).SyncRoot) { if (HasIdleConnections) driver = idlePool.Pop(); } // 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; } /// /// 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.ConnectionLifetimeExpired() || beingCleared) { driver.Close(); Debug.Assert(!idlePool.Contains(driver)); } else { lock ((idlePool as ICollection).SyncRoot) { PushIdle(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) { MySqlTrace.LogError(-1, ex.Message); 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.Pop(); 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. } } /// /// Remove expired drivers from the idle pool /// /// /// /// 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) /// internal List RemoveOldIdleConnections() { DateTime now = DateTime.Now; lock ((idlePool as ICollection).SyncRoot) { // Rebuild the Stack with only newly used drivers, // and return old idle drivers List oldDrivers = new List(idlePool.ToArray()); idlePool.Clear(); // Newest driver at index 0, iterate in reverse order for (int i = oldDrivers.Count - 1; i >= 0; i--) { Driver d = oldDrivers[i]; DateTime expirationTime = d.IdleSince.Add( TimeSpan.FromSeconds(MySqlPoolManager.maxConnectionIdleTime)); if ((i < minSize) || (expirationTime > now)) { idlePool.Push(d); oldDrivers.Remove(d); } } return oldDrivers; } } } }