You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

323 lines
11 KiB

4 years ago
  1. #if MYSQL_6_10
  2. // Copyright ?2004, 2016, Oracle and/or its affiliates. All rights reserved.
  3. //
  4. // MySQL Connector/NET is licensed under the terms of the GPLv2
  5. // <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
  6. // MySQL Connectors. There are special exceptions to the terms and
  7. // conditions of the GPLv2 as it is applied to this software, see the
  8. // FLOSS License Exception
  9. // <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  10. //
  11. // This program is free software; you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published
  13. // by the Free Software Foundation; version 2 of the License.
  14. //
  15. // This program is distributed in the hope that it will be useful, but
  16. // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  17. // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  18. // for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License along
  21. // with this program; if not, write to the Free Software Foundation, Inc.,
  22. // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  23. using System;
  24. using System.Collections;
  25. using System.Collections.Generic;
  26. using System.Diagnostics;
  27. using System.Threading;
  28. using Externals.MySql.Data.MySqlClient;
  29. namespace Externals.MySql.Data.MySqlClient
  30. {
  31. /// <summary>
  32. /// Summary description for MySqlPool.
  33. /// </summary>
  34. internal sealed class MySqlPool
  35. {
  36. private readonly List<Driver> _inUsePool;
  37. private readonly Queue<Driver> _idlePool;
  38. private readonly uint _minSize;
  39. private readonly uint _maxSize;
  40. private readonly AutoResetEvent _autoEvent;
  41. private int _available;
  42. private void EnqueueIdle(Driver driver)
  43. {
  44. driver.IdleSince = DateTime.Now;
  45. _idlePool.Enqueue(driver);
  46. }
  47. public MySqlPool(MySqlConnectionStringBuilder settings)
  48. {
  49. _minSize = settings.MinimumPoolSize;
  50. _maxSize = settings.MaximumPoolSize;
  51. _available = (int)_maxSize;
  52. _autoEvent = new AutoResetEvent(false);
  53. if (_minSize > _maxSize)
  54. _minSize = _maxSize;
  55. this.Settings = settings;
  56. _inUsePool = new List<Driver>((int)_maxSize);
  57. _idlePool = new Queue<Driver>((int)_maxSize);
  58. // prepopulate the idle pool to minSize
  59. for (int i = 0; i < _minSize; i++)
  60. EnqueueIdle(CreateNewPooledConnection());
  61. ProcedureCache = new ProcedureCache((int)settings.ProcedureCacheSize);
  62. }
  63. #region Properties
  64. public MySqlConnectionStringBuilder Settings { get; set; }
  65. public ProcedureCache ProcedureCache { get; }
  66. /// <summary>
  67. /// It is assumed that this property will only be used from inside an active
  68. /// lock.
  69. /// </summary>
  70. private bool HasIdleConnections => _idlePool.Count > 0;
  71. private int NumConnections => _idlePool.Count + _inUsePool.Count;
  72. /// <summary>
  73. /// Indicates whether this pool is being cleared.
  74. /// </summary>
  75. public bool BeingCleared { get; private set; }
  76. internal Dictionary<string, string> ServerProperties { get; set; }
  77. #endregion
  78. /// <summary>
  79. /// It is assumed that this method is only called from inside an active lock.
  80. /// </summary>
  81. private Driver GetPooledConnection()
  82. {
  83. Driver driver = null;
  84. // if we don't have an idle connection but we have room for a new
  85. // one, then create it here.
  86. lock ((_idlePool as ICollection).SyncRoot)
  87. {
  88. if (HasIdleConnections)
  89. driver = _idlePool.Dequeue();
  90. }
  91. // Obey the connection timeout
  92. if (driver != null)
  93. {
  94. try
  95. {
  96. driver.ResetTimeout((int)Settings.ConnectionTimeout * 1000);
  97. }
  98. catch (Exception)
  99. {
  100. driver.Close();
  101. driver = null;
  102. }
  103. }
  104. if (driver != null)
  105. {
  106. // first check to see that the server is still alive
  107. if (!driver.Ping())
  108. {
  109. driver.Close();
  110. driver = null;
  111. }
  112. else if (Settings.ConnectionReset)
  113. // if the user asks us to ping/reset pooled connections
  114. // do so now
  115. driver.Reset();
  116. }
  117. if (driver == null)
  118. driver = CreateNewPooledConnection();
  119. Debug.Assert(driver != null);
  120. lock ((_inUsePool as ICollection).SyncRoot)
  121. {
  122. _inUsePool.Add(driver);
  123. }
  124. return driver;
  125. }
  126. /// <summary>
  127. /// It is assumed that this method is only called from inside an active lock.
  128. /// </summary>
  129. private Driver CreateNewPooledConnection()
  130. {
  131. Debug.Assert((_maxSize - NumConnections) > 0, "Pool out of sync.");
  132. Driver driver = Driver.Create(Settings);
  133. driver.Pool = this;
  134. return driver;
  135. }
  136. public void ReleaseConnection(Driver driver)
  137. {
  138. lock ((_inUsePool as ICollection).SyncRoot)
  139. {
  140. if (_inUsePool.Contains(driver))
  141. _inUsePool.Remove(driver);
  142. }
  143. if (driver.ConnectionLifetimeExpired() || BeingCleared)
  144. {
  145. driver.Close();
  146. Debug.Assert(!_idlePool.Contains(driver));
  147. }
  148. else
  149. {
  150. lock ((_idlePool as ICollection).SyncRoot)
  151. {
  152. EnqueueIdle(driver);
  153. }
  154. }
  155. Interlocked.Increment(ref _available);
  156. _autoEvent.Set();
  157. }
  158. /// <summary>
  159. /// Removes a connection from the in use pool. The only situations where this method
  160. /// would be called are when a connection that is in use gets some type of fatal exception
  161. /// or when the connection is being returned to the pool and it's too old to be
  162. /// returned.
  163. /// </summary>
  164. /// <param name="driver"></param>
  165. public void RemoveConnection(Driver driver)
  166. {
  167. lock ((_inUsePool as ICollection).SyncRoot)
  168. {
  169. if (_inUsePool.Contains(driver))
  170. {
  171. _inUsePool.Remove(driver);
  172. Interlocked.Increment(ref _available);
  173. _autoEvent.Set();
  174. }
  175. }
  176. // if we are being cleared and we are out of connections then have
  177. // the manager destroy us.
  178. if (BeingCleared && NumConnections == 0)
  179. MySqlPoolManager.RemoveClearedPool(this);
  180. }
  181. private Driver TryToGetDriver()
  182. {
  183. int count = Interlocked.Decrement(ref _available);
  184. if (count < 0)
  185. {
  186. Interlocked.Increment(ref _available);
  187. return null;
  188. }
  189. try
  190. {
  191. Driver driver = GetPooledConnection();
  192. return driver;
  193. }
  194. catch (Exception ex)
  195. {
  196. MySqlTrace.LogError(-1, ex.Message);
  197. Interlocked.Increment(ref _available);
  198. throw;
  199. }
  200. }
  201. public Driver GetConnection()
  202. {
  203. int fullTimeOut = (int)Settings.ConnectionTimeout * 1000;
  204. int timeOut = fullTimeOut;
  205. DateTime start = DateTime.Now;
  206. while (timeOut > 0)
  207. {
  208. Driver driver = TryToGetDriver();
  209. if (driver != null) return driver;
  210. // We have no tickets right now, lets wait for one.
  211. #if NETSTANDARD1_3
  212. if (!_autoEvent.WaitOne(timeOut)) break;
  213. #else
  214. if (!_autoEvent.WaitOne(timeOut, false)) break;
  215. #endif
  216. timeOut = fullTimeOut - (int)DateTime.Now.Subtract(start).TotalMilliseconds;
  217. }
  218. throw new MySqlException(Resources.TimeoutGettingConnection);
  219. }
  220. /// <summary>
  221. /// Clears this pool of all idle connections and marks this pool and being cleared
  222. /// so all other connections are closed when they are returned.
  223. /// </summary>
  224. internal void Clear()
  225. {
  226. lock ((_idlePool as ICollection).SyncRoot)
  227. {
  228. // first, mark ourselves as being cleared
  229. BeingCleared = true;
  230. // then we remove all connections sitting in the idle pool
  231. while (_idlePool.Count > 0)
  232. {
  233. Driver d = _idlePool.Dequeue();
  234. d.Close();
  235. }
  236. // there is nothing left to do here. Now we just wait for all
  237. // in use connections to be returned to the pool. When they are
  238. // they will be closed. When the last one is closed, the pool will
  239. // be destroyed.
  240. }
  241. }
  242. /// <summary>
  243. /// Remove expired drivers from the idle pool
  244. /// </summary>
  245. /// <returns></returns>
  246. /// <remarks>
  247. /// Closing driver is a potentially lengthy operation involving network
  248. /// IO. Therefore we do not close expired drivers while holding
  249. /// idlePool.SyncRoot lock. We just remove the old drivers from the idle
  250. /// queue and return them to the caller. The caller will need to close
  251. /// them (or let GC close them)
  252. /// </remarks>
  253. internal List<Driver> RemoveOldIdleConnections()
  254. {
  255. List<Driver> oldDrivers = new List<Driver>();
  256. DateTime now = DateTime.Now;
  257. lock ((_idlePool as ICollection).SyncRoot)
  258. {
  259. // The drivers appear to be ordered by their age, i.e it is
  260. // sufficient to remove them until the first element is not
  261. // too old.
  262. while (_idlePool.Count > _minSize)
  263. {
  264. Driver d = _idlePool.Peek();
  265. DateTime expirationTime = d.IdleSince.Add(
  266. new TimeSpan(0, 0, MySqlPoolManager.maxConnectionIdleTime));
  267. if (expirationTime.CompareTo(now) < 0)
  268. {
  269. oldDrivers.Add(d);
  270. _idlePool.Dequeue();
  271. }
  272. else
  273. {
  274. break;
  275. }
  276. }
  277. }
  278. return oldDrivers;
  279. }
  280. }
  281. }
  282. #endif