DbConnectionPoolGroup.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. //------------------------------------------------------------------------------
  2. // <copyright file="DbConnectionPoolGroup.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">[....]</owner>
  6. //------------------------------------------------------------------------------
  7. namespace System.Data.ProviderBase {
  8. using System;
  9. using System.Collections.Concurrent;
  10. using System.Data.Common;
  11. using System.Diagnostics;
  12. using System.Threading;
  13. // set_ConnectionString calls DbConnectionFactory.GetConnectionPoolGroup
  14. // when not found a new pool entry is created and potentially added
  15. // DbConnectionPoolGroup starts in the Active state
  16. // Open calls DbConnectionFactory.GetConnectionPool
  17. // if the existing pool entry is Disabled, GetConnectionPoolGroup is called for a new entry
  18. // DbConnectionFactory.GetConnectionPool calls DbConnectionPoolGroup.GetConnectionPool
  19. // DbConnectionPoolGroup.GetConnectionPool will return pool for the current identity
  20. // or null if identity is restricted or pooling is disabled or state is disabled at time of add
  21. // state changes are Active->Active, Idle->Active
  22. // DbConnectionFactory.PruneConnectionPoolGroups calls Prune
  23. // which will QueuePoolForRelease on all empty pools
  24. // and once no pools remain, change state from Active->Idle->Disabled
  25. // Once Disabled, factory can remove its reference to the pool entry
  26. sealed internal class DbConnectionPoolGroup {
  27. private readonly DbConnectionOptions _connectionOptions;
  28. private readonly DbConnectionPoolKey _poolKey;
  29. private readonly DbConnectionPoolGroupOptions _poolGroupOptions;
  30. private ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool> _poolCollection;
  31. private int _state; // see PoolGroupState* below
  32. private DbConnectionPoolGroupProviderInfo _providerInfo;
  33. private DbMetaDataFactory _metaDataFactory;
  34. private static int _objectTypeCount; // Bid counter
  35. internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
  36. // always lock this before changing _state, we don't want to move out of the 'Disabled' state
  37. // PoolGroupStateUninitialized = 0;
  38. private const int PoolGroupStateActive = 1; // initial state, GetPoolGroup from cache, connection Open
  39. private const int PoolGroupStateIdle = 2; // all pools are pruned via Clear
  40. private const int PoolGroupStateDisabled = 4; // factory pool entry prunning method
  41. internal DbConnectionPoolGroup (DbConnectionOptions connectionOptions, DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolGroupOptions) {
  42. Debug.Assert(null != connectionOptions, "null connection options");
  43. Debug.Assert(null == poolGroupOptions || ADP.IsWindowsNT, "should not have pooling options on Win9x");
  44. _connectionOptions = connectionOptions;
  45. _poolKey = key;
  46. _poolGroupOptions = poolGroupOptions;
  47. // always lock this object before changing state
  48. // HybridDictionary does not create any sub-objects until add
  49. // so it is safe to use for non-pooled connection as long as
  50. // we check _poolGroupOptions first
  51. _poolCollection = new ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool>();
  52. _state = PoolGroupStateActive; // VSWhidbey 112102
  53. }
  54. internal DbConnectionOptions ConnectionOptions {
  55. get {
  56. return _connectionOptions;
  57. }
  58. }
  59. internal DbConnectionPoolKey PoolKey {
  60. get {
  61. return _poolKey;
  62. }
  63. }
  64. internal DbConnectionPoolGroupProviderInfo ProviderInfo {
  65. get {
  66. return _providerInfo;
  67. }
  68. set {
  69. _providerInfo = value;
  70. if(null!=value) {
  71. _providerInfo.PoolGroup = this;
  72. }
  73. }
  74. }
  75. internal bool IsDisabled {
  76. get {
  77. return (PoolGroupStateDisabled == _state);
  78. }
  79. }
  80. internal int ObjectID {
  81. get {
  82. return _objectID;
  83. }
  84. }
  85. internal DbConnectionPoolGroupOptions PoolGroupOptions {
  86. get {
  87. return _poolGroupOptions;
  88. }
  89. }
  90. internal DbMetaDataFactory MetaDataFactory{
  91. get {
  92. return _metaDataFactory;
  93. }
  94. set {
  95. _metaDataFactory = value;
  96. }
  97. }
  98. internal int Clear() {
  99. // must be multi-thread safe with competing calls by Clear and Prune via background thread
  100. // will return the number of connections in the group after clearing has finished
  101. // First, note the old collection and create a new collection to be used
  102. ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool> oldPoolCollection = null;
  103. lock (this) {
  104. if (_poolCollection.Count > 0) {
  105. oldPoolCollection = _poolCollection;
  106. _poolCollection = new ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool>();
  107. }
  108. }
  109. // Then, if a new collection was created, release the pools from the old collection
  110. if (oldPoolCollection != null) {
  111. foreach (var entry in oldPoolCollection) {
  112. DbConnectionPool pool = entry.Value;
  113. if (pool != null) {
  114. //
  115. DbConnectionFactory connectionFactory = pool.ConnectionFactory;
  116. #if !MOBILE
  117. connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Decrement();
  118. #endif
  119. connectionFactory.QueuePoolForRelease(pool, true);
  120. }
  121. }
  122. }
  123. // Finally, return the pool collection count - this may be non-zero if something was added while we were clearing
  124. return _poolCollection.Count;
  125. }
  126. internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactory) {
  127. // When this method returns null it indicates that the connection
  128. // factory should not use pooling.
  129. // We don't support connection pooling on Win9x; it lacks too
  130. // many of the APIs we require.
  131. // PoolGroupOptions will only be null when we're not supposed to pool
  132. // connections.
  133. DbConnectionPool pool = null;
  134. if (null != _poolGroupOptions) {
  135. Debug.Assert(ADP.IsWindowsNT, "should not be pooling on Win9x");
  136. DbConnectionPoolIdentity currentIdentity = DbConnectionPoolIdentity.NoIdentity;
  137. if (_poolGroupOptions.PoolByIdentity) {
  138. // if we're pooling by identity (because integrated security is
  139. // being used for these connections) then we need to go out and
  140. // search for the connectionPool that matches the current identity.
  141. currentIdentity = DbConnectionPoolIdentity.GetCurrent();
  142. // If the current token is restricted in some way, then we must
  143. // not attempt to pool these connections.
  144. if (currentIdentity.IsRestricted) {
  145. currentIdentity = null;
  146. }
  147. }
  148. if (null != currentIdentity) {
  149. if (!_poolCollection.TryGetValue(currentIdentity, out pool)) { // find the pool
  150. DbConnectionPoolProviderInfo connectionPoolProviderInfo = connectionFactory.CreateConnectionPoolProviderInfo(this.ConnectionOptions);
  151. // optimistically create pool, but its callbacks are delayed until after actual add
  152. DbConnectionPool newPool = new DbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo);
  153. lock (this) {
  154. // Did someone already add it to the list?
  155. if (!_poolCollection.TryGetValue(currentIdentity, out pool)) {
  156. if (MarkPoolGroupAsActive()) {
  157. // If we get here, we know for certain that we there isn't
  158. // a pool that matches the current identity, so we have to
  159. // add the optimistically created one
  160. newPool.Startup(); // must start pool before usage
  161. bool addResult = _poolCollection.TryAdd(currentIdentity, newPool);
  162. Debug.Assert(addResult, "No other pool with current identity should exist at this point");
  163. #if !MOBILE
  164. connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Increment();
  165. #endif
  166. pool = newPool;
  167. newPool = null;
  168. }
  169. else {
  170. // else pool entry has been disabled so don't create new pools
  171. Debug.Assert(PoolGroupStateDisabled == _state, "state should be disabled");
  172. }
  173. }
  174. else {
  175. // else found an existing pool to use instead
  176. Debug.Assert(PoolGroupStateActive == _state, "state should be active since a pool exists and lock holds");
  177. }
  178. }
  179. if (null != newPool) {
  180. // don't need to call connectionFactory.QueuePoolForRelease(newPool) because
  181. // pool callbacks were delayed and no risk of connections being created
  182. newPool.Shutdown();
  183. }
  184. }
  185. // the found pool could be in any state
  186. }
  187. }
  188. if (null == pool) {
  189. lock(this) {
  190. // keep the pool entry state active when not pooling
  191. MarkPoolGroupAsActive();
  192. }
  193. }
  194. return pool;
  195. }
  196. private bool MarkPoolGroupAsActive() {
  197. // when getting a connection, make the entry active if it was idle (but not disabled)
  198. // must always lock this before calling
  199. if (PoolGroupStateIdle == _state) {
  200. _state = PoolGroupStateActive;
  201. Bid.Trace("<prov.DbConnectionPoolGroup.ClearInternal|RES|INFO|CPOOL> %d#, Active\n", ObjectID);
  202. }
  203. return (PoolGroupStateActive == _state);
  204. }
  205. internal bool Prune() {
  206. // must only call from DbConnectionFactory.PruneConnectionPoolGroups on background timer thread
  207. // must lock(DbConnectionFactory._connectionPoolGroups.SyncRoot) before calling ReadyToRemove
  208. // to avoid conflict with DbConnectionFactory.CreateConnectionPoolGroup replacing pool entry
  209. lock (this) {
  210. if (_poolCollection.Count > 0) {
  211. var newPoolCollection = new ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool>();
  212. foreach (var entry in _poolCollection) {
  213. DbConnectionPool pool = entry.Value;
  214. if (pool != null) {
  215. //
  216. // Actually prune the pool if there are no connections in the pool and no errors occurred.
  217. // Empty pool during pruning indicates zero or low activity, but
  218. // an error state indicates the pool needs to stay around to
  219. // throttle new connection attempts.
  220. if ((!pool.ErrorOccurred) && (0 == pool.Count)) {
  221. // Order is important here. First we remove the pool
  222. // from the collection of pools so no one will try
  223. // to use it while we're processing and finally we put the
  224. // pool into a list of pools to be released when they
  225. // are completely empty.
  226. DbConnectionFactory connectionFactory = pool.ConnectionFactory;
  227. #if !MOBILE
  228. connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Decrement();
  229. #endif
  230. connectionFactory.QueuePoolForRelease(pool, false);
  231. }
  232. else {
  233. newPoolCollection.TryAdd(entry.Key, entry.Value);
  234. }
  235. }
  236. }
  237. _poolCollection = newPoolCollection;
  238. }
  239. // must be pruning thread to change state and no connections
  240. // otherwise pruning thread risks making entry disabled soon after user calls ClearPool
  241. if (0 == _poolCollection.Count) {
  242. if (PoolGroupStateActive == _state) {
  243. _state = PoolGroupStateIdle;
  244. Bid.Trace("<prov.DbConnectionPoolGroup.ClearInternal|RES|INFO|CPOOL> %d#, Idle\n", ObjectID);
  245. }
  246. else if (PoolGroupStateIdle == _state) {
  247. _state = PoolGroupStateDisabled;
  248. Bid.Trace("<prov.DbConnectionPoolGroup.ReadyToRemove|RES|INFO|CPOOL> %d#, Disabled\n", ObjectID);
  249. }
  250. }
  251. return (PoolGroupStateDisabled == _state);
  252. }
  253. }
  254. }
  255. }