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