DbConnectionFactory.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. //------------------------------------------------------------------------------
  2. // <copyright file="DbConnectionFactory.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">[....]</owner>
  6. // <owner current="true" primary="false">[....]</owner>
  7. //------------------------------------------------------------------------------
  8. namespace System.Data.ProviderBase {
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Diagnostics;
  12. using System.Data.Common;
  13. using System.Linq;
  14. using System.Threading;
  15. using System.Threading.Tasks;
  16. internal abstract class DbConnectionFactory {
  17. private Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> _connectionPoolGroups;
  18. private readonly List<DbConnectionPool> _poolsToRelease;
  19. private readonly List<DbConnectionPoolGroup> _poolGroupsToRelease;
  20. private readonly DbConnectionPoolCounters _performanceCounters;
  21. private readonly Timer _pruningTimer;
  22. private const int PruningDueTime =4*60*1000; // 4 minutes
  23. private const int PruningPeriod = 30*1000; // thirty seconds
  24. private static int _objectTypeCount; // Bid counter
  25. internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
  26. // s_pendingOpenNonPooled is an array of tasks used to throttle creation of non-pooled connections to
  27. // a maximum of Environment.ProcessorCount at a time.
  28. static int s_pendingOpenNonPooledNext = 0;
  29. static Task<DbConnectionInternal>[] s_pendingOpenNonPooled = new Task<DbConnectionInternal>[Environment.ProcessorCount];
  30. static Task<DbConnectionInternal> s_completedTask;
  31. protected DbConnectionFactory() : this (DbConnectionPoolCountersNoCounters.SingletonInstance) { }
  32. protected DbConnectionFactory(DbConnectionPoolCounters performanceCounters) {
  33. _performanceCounters = performanceCounters;
  34. _connectionPoolGroups = new Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup>();
  35. _poolsToRelease = new List<DbConnectionPool>();
  36. _poolGroupsToRelease = new List<DbConnectionPoolGroup>();
  37. _pruningTimer = CreatePruningTimer();
  38. }
  39. internal DbConnectionPoolCounters PerformanceCounters {
  40. get { return _performanceCounters; }
  41. }
  42. abstract public DbProviderFactory ProviderFactory {
  43. get;
  44. }
  45. internal int ObjectID {
  46. get {
  47. return _objectID;
  48. }
  49. }
  50. public void ClearAllPools() {
  51. IntPtr hscp;
  52. Bid.ScopeEnter(out hscp, "<prov.DbConnectionFactory.ClearAllPools|API> ");
  53. try {
  54. Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups;
  55. foreach (KeyValuePair<DbConnectionPoolKey, DbConnectionPoolGroup> entry in connectionPoolGroups) {
  56. DbConnectionPoolGroup poolGroup = entry.Value;
  57. if (null != poolGroup) {
  58. poolGroup.Clear();
  59. }
  60. }
  61. }
  62. finally {
  63. Bid.ScopeLeave(ref hscp);
  64. }
  65. }
  66. public void ClearPool(DbConnection connection) {
  67. ADP.CheckArgumentNull(connection, "connection");
  68. IntPtr hscp;
  69. Bid.ScopeEnter(out hscp, "<prov.DbConnectionFactory.ClearPool|API> %d#" , GetObjectId(connection));
  70. try {
  71. DbConnectionPoolGroup poolGroup = GetConnectionPoolGroup(connection);
  72. if (null != poolGroup) {
  73. poolGroup.Clear();
  74. }
  75. }
  76. finally {
  77. Bid.ScopeLeave(ref hscp);
  78. }
  79. }
  80. public void ClearPool(DbConnectionPoolKey key) {
  81. Debug.Assert(key != null, "key cannot be null");
  82. ADP.CheckArgumentNull(key.ConnectionString, "key.ConnectionString");
  83. IntPtr hscp;
  84. Bid.ScopeEnter(out hscp, "<prov.DbConnectionFactory.ClearPool|API> connectionString");
  85. try {
  86. DbConnectionPoolGroup poolGroup;
  87. Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups;
  88. if (connectionPoolGroups.TryGetValue(key, out poolGroup)) {
  89. poolGroup.Clear();
  90. }
  91. }
  92. finally {
  93. Bid.ScopeLeave(ref hscp);
  94. }
  95. }
  96. internal virtual DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions){
  97. return null;
  98. }
  99. virtual protected DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection, out bool cacheMetaDataFactory) {
  100. // providers that support GetSchema must override this with a method that creates a meta data
  101. // factory appropriate for them.
  102. cacheMetaDataFactory = false;
  103. throw ADP.NotSupported();
  104. }
  105. internal DbConnectionInternal CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, DbConnectionOptions userOptions) {
  106. Debug.Assert(null != owningConnection, "null owningConnection?");
  107. Debug.Assert(null != poolGroup, "null poolGroup?");
  108. DbConnectionOptions connectionOptions = poolGroup.ConnectionOptions;
  109. DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = poolGroup.ProviderInfo;
  110. DbConnectionPoolKey poolKey = poolGroup.PoolKey;
  111. DbConnectionInternal newConnection = CreateConnection(connectionOptions, poolKey, poolGroupProviderInfo, null, owningConnection, userOptions);
  112. if (null != newConnection) {
  113. #if !MOBILE
  114. PerformanceCounters.HardConnectsPerSecond.Increment();
  115. #endif
  116. newConnection.MakeNonPooledObject(owningConnection, PerformanceCounters);
  117. }
  118. Bid.Trace("<prov.DbConnectionFactory.CreateNonPooledConnection|RES|CPOOL> %d#, Non-pooled database connection created.\n", ObjectID);
  119. return newConnection;
  120. }
  121. internal DbConnectionInternal CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions) {
  122. Debug.Assert(null != pool, "null pool?");
  123. DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = pool.PoolGroup.ProviderInfo;
  124. DbConnectionInternal newConnection = CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningObject, userOptions);
  125. if (null != newConnection) {
  126. #if !MOBILE
  127. PerformanceCounters.HardConnectsPerSecond.Increment();
  128. #endif
  129. newConnection.MakePooledConnection(pool);
  130. }
  131. Bid.Trace("<prov.DbConnectionFactory.CreatePooledConnection|RES|CPOOL> %d#, Pooled database connection created.\n", ObjectID);
  132. return newConnection;
  133. }
  134. virtual internal DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo (DbConnectionOptions connectionOptions) {
  135. return null;
  136. }
  137. private Timer CreatePruningTimer() {
  138. TimerCallback callback = new TimerCallback(PruneConnectionPoolGroups);
  139. return new Timer(callback, null, PruningDueTime, PruningPeriod);
  140. }
  141. protected DbConnectionOptions FindConnectionOptions(DbConnectionPoolKey key) {
  142. Debug.Assert(key != null, "key cannot be null");
  143. if (!ADP.IsEmpty(key.ConnectionString)) {
  144. DbConnectionPoolGroup connectionPoolGroup;
  145. Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups;
  146. if (connectionPoolGroups.TryGetValue(key, out connectionPoolGroup)) {
  147. return connectionPoolGroup.ConnectionOptions;
  148. }
  149. }
  150. return null;
  151. }
  152. // GetCompletedTask must be called from within s_pendingOpenPooled lock
  153. static Task<DbConnectionInternal> GetCompletedTask()
  154. {
  155. if (s_completedTask == null) {
  156. TaskCompletionSource<DbConnectionInternal> source = new TaskCompletionSource<DbConnectionInternal>();
  157. source.SetResult(null);
  158. s_completedTask = source.Task;
  159. }
  160. return s_completedTask;
  161. }
  162. internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, out DbConnectionInternal connection) {
  163. Debug.Assert(null != owningConnection, "null owningConnection?");
  164. DbConnectionPoolGroup poolGroup;
  165. DbConnectionPool connectionPool;
  166. connection = null;
  167. // SQLBU 431251:
  168. // Work around race condition with clearing the pool between GetConnectionPool obtaining pool
  169. // and GetConnection on the pool checking the pool state. Clearing the pool in this window
  170. // will switch the pool into the ShuttingDown state, and GetConnection will return null.
  171. // There is probably a better solution involving locking the pool/group, but that entails a major
  172. // re-design of the connection pooling synchronization, so is post-poned for now.
  173. // VSDD 674236: use retriesLeft to prevent CPU spikes with incremental sleep
  174. // start with one msec, double the time every retry
  175. // max time is: 1 + 2 + 4 + ... + 2^(retries-1) == 2^retries -1 == 1023ms (for 10 retries)
  176. int retriesLeft = 10;
  177. int timeBetweenRetriesMilliseconds = 1;
  178. do {
  179. poolGroup = GetConnectionPoolGroup(owningConnection);
  180. // Doing this on the callers thread is important because it looks up the WindowsIdentity from the thread.
  181. connectionPool = GetConnectionPool(owningConnection, poolGroup);
  182. if (null == connectionPool) {
  183. // If GetConnectionPool returns null, we can be certain that
  184. // this connection should not be pooled via DbConnectionPool
  185. // or have a disabled pool entry.
  186. poolGroup = GetConnectionPoolGroup(owningConnection); // previous entry have been disabled
  187. if (retry != null) {
  188. Task<DbConnectionInternal> newTask;
  189. CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
  190. lock (s_pendingOpenNonPooled) {
  191. // look for an available task slot (completed or empty)
  192. int idx;
  193. for (idx=0; idx<s_pendingOpenNonPooled.Length; idx++) {
  194. Task task = s_pendingOpenNonPooled[idx];
  195. if (task == null) {
  196. s_pendingOpenNonPooled[idx] = GetCompletedTask();
  197. break;
  198. }
  199. else if (task.IsCompleted) {
  200. break;
  201. }
  202. }
  203. // if didn't find one, pick the next one in round-robbin fashion
  204. if (idx == s_pendingOpenNonPooled.Length) {
  205. idx = s_pendingOpenNonPooledNext++ % s_pendingOpenNonPooled.Length;
  206. }
  207. // now that we have an antecedent task, schedule our work when it is completed.
  208. // If it is a new slot or a compelted task, this continuation will start right away.
  209. //
  210. newTask = s_pendingOpenNonPooled[idx].ContinueWith((_) => {
  211. Transactions.Transaction originalTransaction = ADP.GetCurrentTransaction();
  212. try
  213. {
  214. ADP.SetCurrentTransaction(retry.Task.AsyncState as Transactions.Transaction);
  215. var newConnection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions);
  216. if ((oldConnection != null) && (oldConnection.State == ConnectionState.Open)) {
  217. oldConnection.PrepareForReplaceConnection();
  218. oldConnection.Dispose();
  219. }
  220. return newConnection;
  221. } finally {
  222. ADP.SetCurrentTransaction(originalTransaction);
  223. }
  224. }, cancellationTokenSource.Token, TaskContinuationOptions.LongRunning, TaskScheduler.Default);
  225. // Place this new task in the slot so any future work will be queued behind it
  226. s_pendingOpenNonPooled[idx] = newTask;
  227. }
  228. // Set up the timeout (if needed)
  229. if (owningConnection.ConnectionTimeout > 0) {
  230. int connectionTimeoutMilliseconds = owningConnection.ConnectionTimeout * 1000;
  231. cancellationTokenSource.CancelAfter(connectionTimeoutMilliseconds);
  232. }
  233. // once the task is done, propagate the final results to the original caller
  234. newTask.ContinueWith((task) => {
  235. cancellationTokenSource.Dispose();
  236. if (task.IsCanceled) {
  237. retry.TrySetException(ADP.ExceptionWithStackTrace(ADP.NonPooledOpenTimeout()));
  238. } else if (task.IsFaulted) {
  239. retry.TrySetException(task.Exception.InnerException);
  240. }
  241. else {
  242. if (retry.TrySetResult(task.Result)) {
  243. #if !MOBILE
  244. PerformanceCounters.NumberOfNonPooledConnections.Increment();
  245. #endif
  246. }
  247. else {
  248. // The outer TaskCompletionSource was already completed
  249. // Which means that we don't know if someone has messed with the outer connection in the middle of creation
  250. // So the best thing to do now is to destroy the newly created connection
  251. task.Result.DoomThisConnection();
  252. task.Result.Dispose();
  253. }
  254. }
  255. }, TaskScheduler.Default);
  256. return false;
  257. }
  258. connection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions);
  259. #if !MOBILE
  260. PerformanceCounters.NumberOfNonPooledConnections.Increment();
  261. #endif
  262. }
  263. else {
  264. if (owningConnection.ForceNewConnection) {
  265. Debug.Assert(!(oldConnection is DbConnectionClosed), "Force new connection, but there is no old connection");
  266. connection = connectionPool.ReplaceConnection(owningConnection, userOptions, oldConnection);
  267. }
  268. else {
  269. if (!connectionPool.TryGetConnection(owningConnection, retry, userOptions, out connection)) {
  270. return false;
  271. }
  272. }
  273. if (connection == null) {
  274. // connection creation failed on semaphore waiting or if max pool reached
  275. if (connectionPool.IsRunning) {
  276. // If GetConnection failed while the pool is running, the pool timeout occurred.
  277. Bid.Trace("<prov.DbConnectionFactory.GetConnection|RES|CPOOL> %d#, GetConnection failed because a pool timeout occurred.\n", ObjectID);
  278. throw ADP.PooledOpenTimeout();
  279. }
  280. else {
  281. // We've hit the race condition, where the pool was shut down after we got it from the group.
  282. // Yield time slice to allow shut down activities to complete and a new, running pool to be instantiated
  283. // before retrying.
  284. Threading.Thread.Sleep(timeBetweenRetriesMilliseconds);
  285. timeBetweenRetriesMilliseconds *= 2; // double the wait time for next iteration
  286. }
  287. }
  288. }
  289. } while (connection == null && retriesLeft-- > 0);
  290. if (connection == null) {
  291. // exhausted all retries or timed out - give up
  292. Bid.Trace("<prov.DbConnectionFactory.GetConnection|RES|CPOOL> %d#, GetConnection failed because a pool timeout occurred and all retries were exhausted.\n", ObjectID);
  293. throw ADP.PooledOpenTimeout();
  294. }
  295. return true;
  296. }
  297. private DbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup) {
  298. // if poolgroup is disabled, it will be replaced with a new entry
  299. Debug.Assert(null != owningObject, "null owningObject?");
  300. Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup?");
  301. // It is possible that while the outer connection object has
  302. // been sitting around in a closed and unused state in some long
  303. // running app, the pruner may have come along and remove this
  304. // the pool entry from the master list. If we were to use a
  305. // pool entry in this state, we would create "unmanaged" pools,
  306. // which would be bad. To avoid this problem, we automagically
  307. // re-create the pool entry whenever it's disabled.
  308. // however, don't rebuild connectionOptions if no pooling is involved - let new connections do that work
  309. if (connectionPoolGroup.IsDisabled && (null != connectionPoolGroup.PoolGroupOptions)) {
  310. Bid.Trace("<prov.DbConnectionFactory.GetConnectionPool|RES|INFO|CPOOL> %d#, DisabledPoolGroup=%d#\n", ObjectID, connectionPoolGroup.ObjectID);
  311. // reusing existing pool option in case user originally used SetConnectionPoolOptions
  312. DbConnectionPoolGroupOptions poolOptions = connectionPoolGroup.PoolGroupOptions;
  313. // get the string to hash on again
  314. DbConnectionOptions connectionOptions = connectionPoolGroup.ConnectionOptions;
  315. Debug.Assert(null != connectionOptions, "prevent expansion of connectionString");
  316. connectionPoolGroup = GetConnectionPoolGroup(connectionPoolGroup.PoolKey, poolOptions, ref connectionOptions);
  317. Debug.Assert(null != connectionPoolGroup, "null connectionPoolGroup?");
  318. SetConnectionPoolGroup(owningObject, connectionPoolGroup);
  319. }
  320. DbConnectionPool connectionPool = connectionPoolGroup.GetConnectionPool(this);
  321. return connectionPool;
  322. }
  323. internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolOptions, ref DbConnectionOptions userConnectionOptions) {
  324. if (ADP.IsEmpty(key.ConnectionString)) {
  325. return (DbConnectionPoolGroup)null;
  326. }
  327. DbConnectionPoolGroup connectionPoolGroup;
  328. Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups;
  329. if (!connectionPoolGroups.TryGetValue(key, out connectionPoolGroup) || (connectionPoolGroup.IsDisabled && (null != connectionPoolGroup.PoolGroupOptions))) {
  330. // If we can't find an entry for the connection string in
  331. // our collection of pool entries, then we need to create a
  332. // new pool entry and add it to our collection.
  333. DbConnectionOptions connectionOptions = CreateConnectionOptions(key.ConnectionString, userConnectionOptions);
  334. if (null == connectionOptions) {
  335. throw ADP.InternalConnectionError(ADP.ConnectionError.ConnectionOptionsMissing);
  336. }
  337. string expandedConnectionString = key.ConnectionString;
  338. if (null == userConnectionOptions) { // we only allow one expansion on the connection string
  339. userConnectionOptions = connectionOptions;
  340. expandedConnectionString = connectionOptions.Expand();
  341. // if the expanded string is same instance (default implementation), the use the already created options
  342. if ((object)expandedConnectionString != (object)key.ConnectionString) {
  343. //
  344. DbConnectionPoolKey newKey = (DbConnectionPoolKey) ((ICloneable) key).Clone();
  345. newKey.ConnectionString = expandedConnectionString;
  346. return GetConnectionPoolGroup(newKey, null, ref userConnectionOptions);
  347. }
  348. }
  349. // We don't support connection pooling on Win9x; it lacks too many of the APIs we require.
  350. if ((null == poolOptions) && ADP.IsWindowsNT) {
  351. if (null != connectionPoolGroup) {
  352. // reusing existing pool option in case user originally used SetConnectionPoolOptions
  353. poolOptions = connectionPoolGroup.PoolGroupOptions;
  354. }
  355. else {
  356. // Note: may return null for non-pooled connections
  357. poolOptions = CreateConnectionPoolGroupOptions(connectionOptions);
  358. }
  359. }
  360. DbConnectionPoolGroup newConnectionPoolGroup = new DbConnectionPoolGroup(connectionOptions, key, poolOptions);
  361. newConnectionPoolGroup.ProviderInfo = CreateConnectionPoolGroupProviderInfo(connectionOptions);
  362. lock (this) {
  363. connectionPoolGroups = _connectionPoolGroups;
  364. if (!connectionPoolGroups.TryGetValue(key, out connectionPoolGroup)) {
  365. // build new dictionary with space for new connection string
  366. Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> newConnectionPoolGroups = new Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup>(1+connectionPoolGroups.Count);
  367. foreach (KeyValuePair<DbConnectionPoolKey, DbConnectionPoolGroup> entry in connectionPoolGroups) {
  368. newConnectionPoolGroups.Add(entry.Key, entry.Value);
  369. }
  370. // lock prevents race condition with PruneConnectionPoolGroups
  371. newConnectionPoolGroups.Add(key, newConnectionPoolGroup);
  372. #if !MOBILE
  373. PerformanceCounters.NumberOfActiveConnectionPoolGroups.Increment();
  374. #endif
  375. connectionPoolGroup = newConnectionPoolGroup;
  376. _connectionPoolGroups = newConnectionPoolGroups;
  377. }
  378. else {
  379. Debug.Assert(!connectionPoolGroup.IsDisabled, "Disabled pool entry discovered");
  380. }
  381. }
  382. Debug.Assert(null != connectionPoolGroup, "how did we not create a pool entry?");
  383. Debug.Assert(null != userConnectionOptions, "how did we not have user connection options?");
  384. }
  385. else if (null == userConnectionOptions) {
  386. userConnectionOptions = connectionPoolGroup.ConnectionOptions;
  387. }
  388. return connectionPoolGroup;
  389. }
  390. internal DbMetaDataFactory GetMetaDataFactory(DbConnectionPoolGroup connectionPoolGroup,DbConnectionInternal internalConnection){
  391. Debug.Assert (connectionPoolGroup != null, "connectionPoolGroup may not be null.");
  392. // get the matadatafactory from the pool entry. If it does not already have one
  393. // create one and save it on the pool entry
  394. DbMetaDataFactory metaDataFactory = connectionPoolGroup.MetaDataFactory;
  395. // consider serializing this so we don't construct multiple metadata factories
  396. // if two threads happen to hit this at the same time. One will be GC'd
  397. if (metaDataFactory == null){
  398. bool allowCache = false;
  399. metaDataFactory = CreateMetaDataFactory(internalConnection, out allowCache);
  400. if (allowCache) {
  401. connectionPoolGroup.MetaDataFactory = metaDataFactory;
  402. }
  403. }
  404. return metaDataFactory;
  405. }
  406. private void PruneConnectionPoolGroups(object state) {
  407. // when debugging this method, expect multiple threads at the same time
  408. if (Bid.AdvancedOn) {
  409. Bid.Trace("<prov.DbConnectionFactory.PruneConnectionPoolGroups|RES|INFO|CPOOL> %d#\n", ObjectID);
  410. }
  411. // First, walk the pool release list and attempt to clear each
  412. // pool, when the pool is finally empty, we dispose of it. If the
  413. // pool isn't empty, it's because there are active connections or
  414. // distributed transactions that need it.
  415. lock (_poolsToRelease) {
  416. if (0 != _poolsToRelease.Count) {
  417. DbConnectionPool[] poolsToRelease = _poolsToRelease.ToArray();
  418. foreach (DbConnectionPool pool in poolsToRelease) {
  419. if (null != pool) {
  420. pool.Clear();
  421. if (0 == pool.Count) {
  422. _poolsToRelease.Remove(pool);
  423. if (Bid.AdvancedOn) {
  424. Bid.Trace("<prov.DbConnectionFactory.PruneConnectionPoolGroups|RES|INFO|CPOOL> %d#, ReleasePool=%d#\n", ObjectID, pool.ObjectID);
  425. }
  426. #if !MOBILE
  427. PerformanceCounters.NumberOfInactiveConnectionPools.Decrement();
  428. #endif
  429. }
  430. }
  431. }
  432. }
  433. }
  434. // Next, walk the pool entry release list and dispose of each
  435. // pool entry when it is finally empty. If the pool entry isn't
  436. // empty, it's because there are active pools that need it.
  437. lock (_poolGroupsToRelease) {
  438. if (0 != _poolGroupsToRelease.Count) {
  439. DbConnectionPoolGroup[] poolGroupsToRelease = _poolGroupsToRelease.ToArray();
  440. foreach (DbConnectionPoolGroup poolGroup in poolGroupsToRelease) {
  441. if (null != poolGroup) {
  442. int poolsLeft = poolGroup.Clear(); // may add entries to _poolsToRelease
  443. if (0 == poolsLeft) {
  444. _poolGroupsToRelease.Remove(poolGroup);
  445. if (Bid.AdvancedOn) {
  446. Bid.Trace("<prov.DbConnectionFactory.PruneConnectionPoolGroups|RES|INFO|CPOOL> %d#, ReleasePoolGroup=%d#\n", ObjectID, poolGroup.ObjectID);
  447. }
  448. #if !MOBILE
  449. PerformanceCounters.NumberOfInactiveConnectionPoolGroups.Decrement();
  450. #endif
  451. }
  452. }
  453. }
  454. }
  455. }
  456. // Finally, we walk through the collection of connection pool entries
  457. // and prune each one. This will cause any empty pools to be put
  458. // into the release list.
  459. lock (this) {
  460. Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> connectionPoolGroups = _connectionPoolGroups;
  461. Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup> newConnectionPoolGroups = new Dictionary<DbConnectionPoolKey, DbConnectionPoolGroup>(connectionPoolGroups.Count);
  462. foreach (KeyValuePair<DbConnectionPoolKey, DbConnectionPoolGroup> entry in connectionPoolGroups) {
  463. if (null != entry.Value) {
  464. Debug.Assert(!entry.Value.IsDisabled, "Disabled pool entry discovered");
  465. // entries start active and go idle during prune if all pools are gone
  466. // move idle entries from last prune pass to a queue for pending release
  467. // otherwise process entry which may move it from active to idle
  468. if (entry.Value.Prune()) { // may add entries to _poolsToRelease
  469. #if !MOBILE
  470. PerformanceCounters.NumberOfActiveConnectionPoolGroups.Decrement();
  471. #endif
  472. QueuePoolGroupForRelease(entry.Value);
  473. }
  474. else {
  475. newConnectionPoolGroups.Add(entry.Key, entry.Value);
  476. }
  477. }
  478. }
  479. _connectionPoolGroups = newConnectionPoolGroups;
  480. }
  481. }
  482. internal void QueuePoolForRelease(DbConnectionPool pool, bool clearing) {
  483. // Queue the pool up for release -- we'll clear it out and dispose
  484. // of it as the last part of the pruning timer callback so we don't
  485. // do it with the pool entry or the pool collection locked.
  486. Debug.Assert (null != pool, "null pool?");
  487. // set the pool to the shutdown state to force all active
  488. // connections to be automatically disposed when they
  489. // are returned to the pool
  490. pool.Shutdown();
  491. lock (_poolsToRelease) {
  492. if (clearing) {
  493. pool.Clear();
  494. }
  495. _poolsToRelease.Add(pool);
  496. }
  497. #if !MOBILE
  498. PerformanceCounters.NumberOfInactiveConnectionPools.Increment();
  499. #endif
  500. }
  501. internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup) {
  502. Debug.Assert (null != poolGroup, "null poolGroup?");
  503. Bid.Trace("<prov.DbConnectionFactory.QueuePoolGroupForRelease|RES|INFO|CPOOL> %d#, poolGroup=%d#\n", ObjectID, poolGroup.ObjectID);
  504. lock (_poolGroupsToRelease) {
  505. _poolGroupsToRelease.Add(poolGroup);
  506. }
  507. #if !MOBILE
  508. PerformanceCounters.NumberOfInactiveConnectionPoolGroups.Increment();
  509. #endif
  510. }
  511. virtual protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) {
  512. return CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningConnection);
  513. }
  514. abstract protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection);
  515. abstract protected DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous);
  516. abstract protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(DbConnectionOptions options);
  517. abstract internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnection connection);
  518. abstract internal DbConnectionInternal GetInnerConnection(DbConnection connection);
  519. abstract protected int GetObjectId(DbConnection connection);
  520. abstract internal void PermissionDemand(DbConnection outerConnection);
  521. abstract internal void SetConnectionPoolGroup(DbConnection outerConnection, DbConnectionPoolGroup poolGroup);
  522. abstract internal void SetInnerConnectionEvent(DbConnection owningObject, DbConnectionInternal to);
  523. abstract internal bool SetInnerConnectionFrom(DbConnection owningObject, DbConnectionInternal to, DbConnectionInternal from) ;
  524. abstract internal void SetInnerConnectionTo(DbConnection owningObject, DbConnectionInternal to);
  525. }
  526. }