// Copyright (c) 2004, 2019, Oracle and/or its affiliates. All rights reserved. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License, version 2.0, as // published by the Free Software Foundation. // // This program is also distributed with certain software (including // but not limited to OpenSSL) that is licensed under separate terms, // as designated in a particular file or component or in included license // documentation. The authors of MySQL hereby grant you an // additional permission to link the program and your derivative works // with the separately licensed software that they have included with // MySQL. // // Without limiting anything contained in the foregoing, this file, // which is part of MySQL Connector/NET, is also subject to the // Universal FOSS Exception, version 1.0, a copy of which can be found at // http://oss.oracle.com/licenses/universal-foss-exception. // // 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, version 2.0, 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 MySql.Data.Failover; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; namespace MySql.Data.MySqlClient { /// /// Summary description for MySqlPoolManager. /// internal class MySqlPoolManager { private static readonly Dictionary Pools = new Dictionary(); private static readonly List ClearingPools = new List(); internal const int DEMOTED_TIMEOUT = 120000; #region Properties /// /// Queue of demoted hosts. /// internal static ConcurrentQueue DemotedHosts { get; set; } /// /// List of hosts that will be attempted to connect to. /// internal static List Hosts { get; set; } /// /// Timer to be used when a host have been demoted. /// internal static Timer DemotedServersTimer { get; set; } #endregion // Timeout in seconds, after which an unused (idle) connection // should be closed. internal static int maxConnectionIdleTime = 180; static MySqlPoolManager() { AppDomain.CurrentDomain.ProcessExit += EnsureClearingPools; AppDomain.CurrentDomain.DomainUnload += EnsureClearingPools; } private static void EnsureClearingPools(object sender, EventArgs e) { ClearAllPools(); } // we add a small amount to the due time to let the cleanup detect // expired connections in the first cleanup. private static Timer timer = new Timer( CleanIdleConnections, null, (maxConnectionIdleTime * 1000) + 8000, maxConnectionIdleTime * 1000); private static string GetKey(MySqlConnectionStringBuilder settings) { string key = ""; lock (settings) { key = settings.ConnectionString; } if (!settings.IntegratedSecurity || settings.ConnectionReset) { return key; } #if !CF && !RT && !NETCOREAPP try { // Append SID to the connection string to generate a key // With Integrated security different Windows users with the same // connection string may be mapped to different MySQL accounts. System.Security.Principal.WindowsIdentity id = System.Security.Principal.WindowsIdentity.GetCurrent(); key += ";" + id.User; } catch (System.Security.SecurityException ex) { // Documentation for WindowsIdentity.GetCurrent() states // SecurityException can be thrown. In this case the // connection can only be pooled if reset is done. throw new MySqlException(Resources.NoWindowsIdentity, ex); } #endif return key; } public static MySqlPool GetPool(MySqlConnectionStringBuilder settings) { string text = GetKey(settings); lock (Pools) { MySqlPool pool; Pools.TryGetValue(text, out pool); if (pool == null) { pool = new MySqlPool(settings); Pools.Add(text, pool); } else { pool.Settings = settings; } return pool; } } public static void RemoveConnection(Driver driver) { Debug.Assert(driver != null); MySqlPool pool = driver.Pool; pool?.RemoveConnection(driver); } public static void ReleaseConnection(Driver driver) { Debug.Assert(driver != null); MySqlPool pool = driver.Pool; pool?.ReleaseConnection(driver); } public static void ClearPool(MySqlConnectionStringBuilder settings) { Debug.Assert(settings != null); string text; try { text = GetKey(settings); } catch (MySqlException) { // Cannot retrieve windows identity for IntegratedSecurity=true // This can be ignored. return; } ClearPoolByText(text); } private static void ClearPoolByText(string key) { lock (Pools) { // if pools doesn't have it, then this pool must already have been cleared if (!Pools.ContainsKey(key)) { return; } // add the pool to our list of pools being cleared MySqlPool pool = (Pools[key] as MySqlPool); ClearingPools.Add(pool); // now tell the pool to clear itself pool.Clear(); // and then remove the pool from the active pools list Pools.Remove(key); } } public static void ClearAllPools() { lock (Pools) { // Create separate keys list. List keys = new List(Pools.Count); keys.AddRange(Pools.Keys); // Remove all pools by key. foreach (string key in keys) { ClearPoolByText(key); } } if (DemotedServersTimer != null) { DemotedServersTimer.Dispose(); Hosts?.Clear(); while (!DemotedHosts.IsEmpty) { DemotedHosts.TryDequeue(out _); } } } public static void RemoveClearedPool(MySqlPool pool) { Debug.Assert(ClearingPools.Contains(pool)); ClearingPools.Remove(pool); } /// /// Remove drivers that have been idle for too long. /// public static void CleanIdleConnections(object obj) { List oldDrivers = new List(); lock (Pools) { foreach (MySqlPool pool in Pools.Keys.Select(key => Pools[key])) { oldDrivers.AddRange(pool.RemoveOldIdleConnections()); } } foreach (Driver driver in oldDrivers) { driver.Close(); } } /// /// Remove hosts that have been on the demoted list for more /// than 120,000 milliseconds and add them to the available hosts list. /// internal static void ReleaseDemotedHosts(object state) { while (!DemotedHosts.IsEmpty) { if (DemotedHosts.TryPeek(out FailoverServer demotedServer) && demotedServer.DemotedTime.AddMilliseconds(DEMOTED_TIMEOUT) < DateTime.Now) { demotedServer.Attempted = false; Hosts?.Add(demotedServer); DemotedHosts.TryDequeue(out demotedServer); } else { break; } } DemotedServersTimer.Change(DEMOTED_TIMEOUT, Timeout.Infinite); } } }