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.

337 lines
9.4 KiB

4 years ago
  1. #if MYSQL_6_9
  2. // Copyright � 2004, 2011, 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.Properties;
  29. namespace Externals.MySql.Data.MySqlClient
  30. {
  31. /// <summary>
  32. /// Summary description for MySqlPool.
  33. /// </summary>
  34. internal sealed class MySqlPool
  35. {
  36. private List<Driver> inUsePool;
  37. private Queue<Driver> idlePool;
  38. private MySqlConnectionStringBuilder settings;
  39. private uint minSize;
  40. private uint maxSize;
  41. private ProcedureCache procedureCache;
  42. private bool beingCleared;
  43. private int available;
  44. private AutoResetEvent autoEvent;
  45. private void EnqueueIdle(Driver driver)
  46. {
  47. driver.IdleSince = DateTime.Now;
  48. idlePool.Enqueue(driver);
  49. }
  50. public MySqlPool(MySqlConnectionStringBuilder settings)
  51. {
  52. minSize = settings.MinimumPoolSize;
  53. maxSize = settings.MaximumPoolSize;
  54. available = (int)maxSize;
  55. autoEvent = new AutoResetEvent(false);
  56. if (minSize > maxSize)
  57. minSize = maxSize;
  58. this.settings = settings;
  59. inUsePool = new List<Driver>((int)maxSize);
  60. idlePool = new Queue<Driver>((int)maxSize);
  61. // prepopulate the idle pool to minSize
  62. for (int i = 0; i < minSize; i++)
  63. EnqueueIdle(CreateNewPooledConnection());
  64. procedureCache = new ProcedureCache((int)settings.ProcedureCacheSize);
  65. }
  66. #region Properties
  67. public MySqlConnectionStringBuilder Settings
  68. {
  69. get { return settings; }
  70. set { settings = value; }
  71. }
  72. public ProcedureCache ProcedureCache
  73. {
  74. get { return procedureCache; }
  75. }
  76. /// <summary>
  77. /// It is assumed that this property will only be used from inside an active
  78. /// lock.
  79. /// </summary>
  80. private bool HasIdleConnections
  81. {
  82. get { return idlePool.Count > 0; }
  83. }
  84. private int NumConnections
  85. {
  86. get { return idlePool.Count + inUsePool.Count; }
  87. }
  88. /// <summary>
  89. /// Indicates whether this pool is being cleared.
  90. /// </summary>
  91. public bool BeingCleared
  92. {
  93. get { return beingCleared; }
  94. }
  95. internal Dictionary<string,string> ServerProperties { get; set; }
  96. #endregion
  97. /// <summary>
  98. /// It is assumed that this method is only called from inside an active lock.
  99. /// </summary>
  100. private Driver GetPooledConnection()
  101. {
  102. Driver driver = null;
  103. // if we don't have an idle connection but we have room for a new
  104. // one, then create it here.
  105. lock ((idlePool as ICollection).SyncRoot)
  106. {
  107. if (HasIdleConnections)
  108. driver = idlePool.Dequeue();
  109. }
  110. // Obey the connection timeout
  111. if (driver != null)
  112. {
  113. try
  114. {
  115. driver.ResetTimeout((int)Settings.ConnectionTimeout * 1000);
  116. }
  117. catch (Exception)
  118. {
  119. driver.Close();
  120. driver = null;
  121. }
  122. }
  123. if (driver != null)
  124. {
  125. // first check to see that the server is still alive
  126. if (!driver.Ping())
  127. {
  128. driver.Close();
  129. driver = null;
  130. }
  131. else if (settings.ConnectionReset)
  132. // if the user asks us to ping/reset pooled connections
  133. // do so now
  134. driver.Reset();
  135. }
  136. if (driver == null)
  137. driver = CreateNewPooledConnection();
  138. Debug.Assert(driver != null);
  139. lock ((inUsePool as ICollection).SyncRoot)
  140. {
  141. inUsePool.Add(driver);
  142. }
  143. return driver;
  144. }
  145. /// <summary>
  146. /// It is assumed that this method is only called from inside an active lock.
  147. /// </summary>
  148. private Driver CreateNewPooledConnection()
  149. {
  150. Debug.Assert((maxSize - NumConnections) > 0, "Pool out of sync.");
  151. Driver driver = Driver.Create(settings);
  152. driver.Pool = this;
  153. return driver;
  154. }
  155. public void ReleaseConnection(Driver driver)
  156. {
  157. lock ((inUsePool as ICollection).SyncRoot)
  158. {
  159. if (inUsePool.Contains(driver))
  160. inUsePool.Remove(driver);
  161. }
  162. if (driver.ConnectionLifetimeExpired() || beingCleared)
  163. {
  164. driver.Close();
  165. Debug.Assert(!idlePool.Contains(driver));
  166. }
  167. else
  168. {
  169. lock ((idlePool as ICollection).SyncRoot)
  170. {
  171. EnqueueIdle(driver);
  172. }
  173. }
  174. Interlocked.Increment(ref available);
  175. autoEvent.Set();
  176. }
  177. /// <summary>
  178. /// Removes a connection from the in use pool. The only situations where this method
  179. /// would be called are when a connection that is in use gets some type of fatal exception
  180. /// or when the connection is being returned to the pool and it's too old to be
  181. /// returned.
  182. /// </summary>
  183. /// <param name="driver"></param>
  184. public void RemoveConnection(Driver driver)
  185. {
  186. lock ((inUsePool as ICollection).SyncRoot)
  187. {
  188. if (inUsePool.Contains(driver))
  189. {
  190. inUsePool.Remove(driver);
  191. Interlocked.Increment(ref available);
  192. autoEvent.Set();
  193. }
  194. }
  195. // if we are being cleared and we are out of connections then have
  196. // the manager destroy us.
  197. if (beingCleared && NumConnections == 0)
  198. MySqlPoolManager.RemoveClearedPool(this);
  199. }
  200. private Driver TryToGetDriver()
  201. {
  202. int count = Interlocked.Decrement(ref available);
  203. if (count < 0)
  204. {
  205. Interlocked.Increment(ref available);
  206. return null;
  207. }
  208. try
  209. {
  210. Driver driver = GetPooledConnection();
  211. return driver;
  212. }
  213. catch (Exception ex)
  214. {
  215. MySqlTrace.LogError(-1, ex.Message);
  216. Interlocked.Increment(ref available);
  217. throw;
  218. }
  219. }
  220. public Driver GetConnection()
  221. {
  222. int fullTimeOut = (int)settings.ConnectionTimeout * 1000;
  223. int timeOut = fullTimeOut;
  224. DateTime start = DateTime.Now;
  225. while (timeOut > 0)
  226. {
  227. Driver driver = TryToGetDriver();
  228. if (driver != null) return driver;
  229. // We have no tickets right now, lets wait for one.
  230. if (!autoEvent.WaitOne(timeOut, false)) break;
  231. timeOut = fullTimeOut - (int)DateTime.Now.Subtract(start).TotalMilliseconds;
  232. }
  233. throw new MySqlException(Resources.TimeoutGettingConnection);
  234. }
  235. /// <summary>
  236. /// Clears this pool of all idle connections and marks this pool and being cleared
  237. /// so all other connections are closed when they are returned.
  238. /// </summary>
  239. internal void Clear()
  240. {
  241. lock ((idlePool as ICollection).SyncRoot)
  242. {
  243. // first, mark ourselves as being cleared
  244. beingCleared = true;
  245. // then we remove all connections sitting in the idle pool
  246. while (idlePool.Count > 0)
  247. {
  248. Driver d = idlePool.Dequeue();
  249. d.Close();
  250. }
  251. // there is nothing left to do here. Now we just wait for all
  252. // in use connections to be returned to the pool. When they are
  253. // they will be closed. When the last one is closed, the pool will
  254. // be destroyed.
  255. }
  256. }
  257. /// <summary>
  258. /// Remove expired drivers from the idle pool
  259. /// </summary>
  260. /// <returns></returns>
  261. /// <remarks>
  262. /// Closing driver is a potentially lengthy operation involving network
  263. /// IO. Therefore we do not close expired drivers while holding
  264. /// idlePool.SyncRoot lock. We just remove the old drivers from the idle
  265. /// queue and return them to the caller. The caller will need to close
  266. /// them (or let GC close them)
  267. /// </remarks>
  268. internal List<Driver> RemoveOldIdleConnections()
  269. {
  270. List<Driver> oldDrivers = new List<Driver>();
  271. DateTime now = DateTime.Now;
  272. lock ((idlePool as ICollection).SyncRoot)
  273. {
  274. // The drivers appear to be ordered by their age, i.e it is
  275. // sufficient to remove them until the first element is not
  276. // too old.
  277. while (idlePool.Count > minSize)
  278. {
  279. Driver d = idlePool.Peek();
  280. DateTime expirationTime = d.IdleSince.Add(
  281. new TimeSpan(0, 0, MySqlPoolManager.maxConnectionIdleTime));
  282. if (expirationTime.CompareTo(now) < 0)
  283. {
  284. oldDrivers.Add(d);
  285. idlePool.Dequeue();
  286. }
  287. else
  288. {
  289. break;
  290. }
  291. }
  292. }
  293. return oldDrivers;
  294. }
  295. }
  296. }
  297. #endif