2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
// <copyright file="DbConnectionFactory.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
2017-08-21 15:34:15 +00:00
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
namespace System.Data.ProviderBase {
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Data.Common ;
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
internal abstract class DbConnectionFactory {
private Dictionary < DbConnectionPoolKey , DbConnectionPoolGroup > _connectionPoolGroups ;
private readonly List < DbConnectionPool > _poolsToRelease ;
private readonly List < DbConnectionPoolGroup > _poolGroupsToRelease ;
private readonly DbConnectionPoolCounters _performanceCounters ;
private readonly Timer _pruningTimer ;
private const int PruningDueTime = 4 * 60 * 1000 ; // 4 minutes
private const int PruningPeriod = 30 * 1000 ; // thirty seconds
private static int _objectTypeCount ; // Bid counter
internal readonly int _objectID = System . Threading . Interlocked . Increment ( ref _objectTypeCount ) ;
// s_pendingOpenNonPooled is an array of tasks used to throttle creation of non-pooled connections to
// a maximum of Environment.ProcessorCount at a time.
static int s_pendingOpenNonPooledNext = 0 ;
static Task < DbConnectionInternal > [ ] s_pendingOpenNonPooled = new Task < DbConnectionInternal > [ Environment . ProcessorCount ] ;
static Task < DbConnectionInternal > s_completedTask ;
protected DbConnectionFactory ( ) : this ( DbConnectionPoolCountersNoCounters . SingletonInstance ) { }
protected DbConnectionFactory ( DbConnectionPoolCounters performanceCounters ) {
_performanceCounters = performanceCounters ;
_connectionPoolGroups = new Dictionary < DbConnectionPoolKey , DbConnectionPoolGroup > ( ) ;
_poolsToRelease = new List < DbConnectionPool > ( ) ;
_poolGroupsToRelease = new List < DbConnectionPoolGroup > ( ) ;
_pruningTimer = CreatePruningTimer ( ) ;
}
internal DbConnectionPoolCounters PerformanceCounters {
get { return _performanceCounters ; }
}
abstract public DbProviderFactory ProviderFactory {
get ;
}
internal int ObjectID {
get {
return _objectID ;
}
}
public void ClearAllPools ( ) {
IntPtr hscp ;
Bid . ScopeEnter ( out hscp , "<prov.DbConnectionFactory.ClearAllPools|API> " ) ;
try {
Dictionary < DbConnectionPoolKey , DbConnectionPoolGroup > connectionPoolGroups = _connectionPoolGroups ;
foreach ( KeyValuePair < DbConnectionPoolKey , DbConnectionPoolGroup > entry in connectionPoolGroups ) {
DbConnectionPoolGroup poolGroup = entry . Value ;
if ( null ! = poolGroup ) {
poolGroup . Clear ( ) ;
}
}
}
finally {
Bid . ScopeLeave ( ref hscp ) ;
}
}
public void ClearPool ( DbConnection connection ) {
ADP . CheckArgumentNull ( connection , "connection" ) ;
IntPtr hscp ;
Bid . ScopeEnter ( out hscp , "<prov.DbConnectionFactory.ClearPool|API> %d#" , GetObjectId ( connection ) ) ;
try {
DbConnectionPoolGroup poolGroup = GetConnectionPoolGroup ( connection ) ;
if ( null ! = poolGroup ) {
poolGroup . Clear ( ) ;
}
}
finally {
Bid . ScopeLeave ( ref hscp ) ;
}
}
public void ClearPool ( DbConnectionPoolKey key ) {
Debug . Assert ( key ! = null , "key cannot be null" ) ;
ADP . CheckArgumentNull ( key . ConnectionString , "key.ConnectionString" ) ;
IntPtr hscp ;
Bid . ScopeEnter ( out hscp , "<prov.DbConnectionFactory.ClearPool|API> connectionString" ) ;
try {
DbConnectionPoolGroup poolGroup ;
Dictionary < DbConnectionPoolKey , DbConnectionPoolGroup > connectionPoolGroups = _connectionPoolGroups ;
if ( connectionPoolGroups . TryGetValue ( key , out poolGroup ) ) {
poolGroup . Clear ( ) ;
}
}
finally {
Bid . ScopeLeave ( ref hscp ) ;
}
}
internal virtual DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo ( DbConnectionOptions connectionOptions ) {
return null ;
}
virtual protected DbMetaDataFactory CreateMetaDataFactory ( DbConnectionInternal internalConnection , out bool cacheMetaDataFactory ) {
// providers that support GetSchema must override this with a method that creates a meta data
// factory appropriate for them.
cacheMetaDataFactory = false ;
throw ADP . NotSupported ( ) ;
}
internal DbConnectionInternal CreateNonPooledConnection ( DbConnection owningConnection , DbConnectionPoolGroup poolGroup , DbConnectionOptions userOptions ) {
Debug . Assert ( null ! = owningConnection , "null owningConnection?" ) ;
Debug . Assert ( null ! = poolGroup , "null poolGroup?" ) ;
DbConnectionOptions connectionOptions = poolGroup . ConnectionOptions ;
DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = poolGroup . ProviderInfo ;
DbConnectionPoolKey poolKey = poolGroup . PoolKey ;
DbConnectionInternal newConnection = CreateConnection ( connectionOptions , poolKey , poolGroupProviderInfo , null , owningConnection , userOptions ) ;
if ( null ! = newConnection ) {
#if ! MOBILE
PerformanceCounters . HardConnectsPerSecond . Increment ( ) ;
#endif
newConnection . MakeNonPooledObject ( owningConnection , PerformanceCounters ) ;
}
Bid . Trace ( "<prov.DbConnectionFactory.CreateNonPooledConnection|RES|CPOOL> %d#, Non-pooled database connection created.\n" , ObjectID ) ;
return newConnection ;
}
internal DbConnectionInternal CreatePooledConnection ( DbConnectionPool pool , DbConnection owningObject , DbConnectionOptions options , DbConnectionPoolKey poolKey , DbConnectionOptions userOptions ) {
Debug . Assert ( null ! = pool , "null pool?" ) ;
DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = pool . PoolGroup . ProviderInfo ;
DbConnectionInternal newConnection = CreateConnection ( options , poolKey , poolGroupProviderInfo , pool , owningObject , userOptions ) ;
if ( null ! = newConnection ) {
#if ! MOBILE
PerformanceCounters . HardConnectsPerSecond . Increment ( ) ;
#endif
newConnection . MakePooledConnection ( pool ) ;
}
Bid . Trace ( "<prov.DbConnectionFactory.CreatePooledConnection|RES|CPOOL> %d#, Pooled database connection created.\n" , ObjectID ) ;
return newConnection ;
}
virtual internal DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo ( DbConnectionOptions connectionOptions ) {
return null ;
}
private Timer CreatePruningTimer ( ) {
TimerCallback callback = new TimerCallback ( PruneConnectionPoolGroups ) ;
return new Timer ( callback , null , PruningDueTime , PruningPeriod ) ;
}
protected DbConnectionOptions FindConnectionOptions ( DbConnectionPoolKey key ) {
Debug . Assert ( key ! = null , "key cannot be null" ) ;
if ( ! ADP . IsEmpty ( key . ConnectionString ) ) {
DbConnectionPoolGroup connectionPoolGroup ;
Dictionary < DbConnectionPoolKey , DbConnectionPoolGroup > connectionPoolGroups = _connectionPoolGroups ;
if ( connectionPoolGroups . TryGetValue ( key , out connectionPoolGroup ) ) {
return connectionPoolGroup . ConnectionOptions ;
}
}
return null ;
}
// GetCompletedTask must be called from within s_pendingOpenPooled lock
static Task < DbConnectionInternal > GetCompletedTask ( )
{
if ( s_completedTask = = null ) {
TaskCompletionSource < DbConnectionInternal > source = new TaskCompletionSource < DbConnectionInternal > ( ) ;
source . SetResult ( null ) ;
s_completedTask = source . Task ;
}
return s_completedTask ;
}
internal bool TryGetConnection ( DbConnection owningConnection , TaskCompletionSource < DbConnectionInternal > retry , DbConnectionOptions userOptions , DbConnectionInternal oldConnection , out DbConnectionInternal connection ) {
Debug . Assert ( null ! = owningConnection , "null owningConnection?" ) ;
DbConnectionPoolGroup poolGroup ;
DbConnectionPool connectionPool ;
connection = null ;
// SQLBU 431251:
// Work around race condition with clearing the pool between GetConnectionPool obtaining pool
// and GetConnection on the pool checking the pool state. Clearing the pool in this window
// will switch the pool into the ShuttingDown state, and GetConnection will return null.
// There is probably a better solution involving locking the pool/group, but that entails a major
// re-design of the connection pooling synchronization, so is post-poned for now.
// VSDD 674236: use retriesLeft to prevent CPU spikes with incremental sleep
// start with one msec, double the time every retry
// max time is: 1 + 2 + 4 + ... + 2^(retries-1) == 2^retries -1 == 1023ms (for 10 retries)
int retriesLeft = 10 ;
int timeBetweenRetriesMilliseconds = 1 ;
do {
poolGroup = GetConnectionPoolGroup ( owningConnection ) ;
// Doing this on the callers thread is important because it looks up the WindowsIdentity from the thread.
connectionPool = GetConnectionPool ( owningConnection , poolGroup ) ;
if ( null = = connectionPool ) {
// If GetConnectionPool returns null, we can be certain that
// this connection should not be pooled via DbConnectionPool
// or have a disabled pool entry.
poolGroup = GetConnectionPoolGroup ( owningConnection ) ; // previous entry have been disabled
if ( retry ! = null ) {
Task < DbConnectionInternal > newTask ;
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource ( ) ;
lock ( s_pendingOpenNonPooled ) {
// look for an available task slot (completed or empty)
int idx ;
for ( idx = 0 ; idx < s_pendingOpenNonPooled . Length ; idx + + ) {
Task task = s_pendingOpenNonPooled [ idx ] ;
if ( task = = null ) {
s_pendingOpenNonPooled [ idx ] = GetCompletedTask ( ) ;
break ;
}
else if ( task . IsCompleted ) {
break ;
}
}
// if didn't find one, pick the next one in round-robbin fashion
if ( idx = = s_pendingOpenNonPooled . Length ) {
idx = s_pendingOpenNonPooledNext + + % s_pendingOpenNonPooled . Length ;
}
// now that we have an antecedent task, schedule our work when it is completed.
// If it is a new slot or a compelted task, this continuation will start right away.
//
newTask = s_pendingOpenNonPooled [ idx ] . ContinueWith ( ( _ ) = > {
Transactions . Transaction originalTransaction = ADP . GetCurrentTransaction ( ) ;
try
{
ADP . SetCurrentTransaction ( retry . Task . AsyncState as Transactions . Transaction ) ;
var newConnection = CreateNonPooledConnection ( owningConnection , poolGroup , userOptions ) ;
if ( ( oldConnection ! = null ) & & ( oldConnection . State = = ConnectionState . Open ) ) {
oldConnection . PrepareForReplaceConnection ( ) ;
oldConnection . Dispose ( ) ;
}
return newConnection ;
} finally {
ADP . SetCurrentTransaction ( originalTransaction ) ;
}
} , cancellationTokenSource . Token , TaskContinuationOptions . LongRunning , TaskScheduler . Default ) ;
// Place this new task in the slot so any future work will be queued behind it
s_pendingOpenNonPooled [ idx ] = newTask ;
}
// Set up the timeout (if needed)
if ( owningConnection . ConnectionTimeout > 0 ) {
int connectionTimeoutMilliseconds = owningConnection . ConnectionTimeout * 1000 ;
cancellationTokenSource . CancelAfter ( connectionTimeoutMilliseconds ) ;
}
// once the task is done, propagate the final results to the original caller
newTask . ContinueWith ( ( task ) = > {
cancellationTokenSource . Dispose ( ) ;
if ( task . IsCanceled ) {
retry . TrySetException ( ADP . ExceptionWithStackTrace ( ADP . NonPooledOpenTimeout ( ) ) ) ;
} else if ( task . IsFaulted ) {
retry . TrySetException ( task . Exception . InnerException ) ;
}
else {
if ( retry . TrySetResult ( task . Result ) ) {
#if ! MOBILE
PerformanceCounters . NumberOfNonPooledConnections . Increment ( ) ;
#endif
}
else {
// The outer TaskCompletionSource was already completed
// Which means that we don't know if someone has messed with the outer connection in the middle of creation
// So the best thing to do now is to destroy the newly created connection
task . Result . DoomThisConnection ( ) ;
task . Result . Dispose ( ) ;
}
}
} , TaskScheduler . Default ) ;
return false ;
}
connection = CreateNonPooledConnection ( owningConnection , poolGroup , userOptions ) ;
#if ! MOBILE
PerformanceCounters . NumberOfNonPooledConnections . Increment ( ) ;
#endif
}
else {
2017-08-21 15:34:15 +00:00
#if ! MONO
// DBConnection::ForceNewConnection is never set
2016-08-03 10:59:49 +00:00
if ( owningConnection . ForceNewConnection ) {
Debug . Assert ( ! ( oldConnection is DbConnectionClosed ) , "Force new connection, but there is no old connection" ) ;
connection = connectionPool . ReplaceConnection ( owningConnection , userOptions , oldConnection ) ;
}
2017-08-21 15:34:15 +00:00
else
#endif
{
2016-08-03 10:59:49 +00:00
if ( ! connectionPool . TryGetConnection ( owningConnection , retry , userOptions , out connection ) ) {
return false ;
}
}
if ( connection = = null ) {
// connection creation failed on semaphore waiting or if max pool reached
if ( connectionPool . IsRunning ) {
// If GetConnection failed while the pool is running, the pool timeout occurred.
Bid . Trace ( "<prov.DbConnectionFactory.GetConnection|RES|CPOOL> %d#, GetConnection failed because a pool timeout occurred.\n" , ObjectID ) ;
throw ADP . PooledOpenTimeout ( ) ;
}
else {
// We've hit the race condition, where the pool was shut down after we got it from the group.
// Yield time slice to allow shut down activities to complete and a new, running pool to be instantiated
// before retrying.
Threading . Thread . Sleep ( timeBetweenRetriesMilliseconds ) ;
timeBetweenRetriesMilliseconds * = 2 ; // double the wait time for next iteration
}
}
}
} while ( connection = = null & & retriesLeft - - > 0 ) ;
if ( connection = = null ) {
// exhausted all retries or timed out - give up
Bid . Trace ( "<prov.DbConnectionFactory.GetConnection|RES|CPOOL> %d#, GetConnection failed because a pool timeout occurred and all retries were exhausted.\n" , ObjectID ) ;
throw ADP . PooledOpenTimeout ( ) ;
}
return true ;
}
private DbConnectionPool GetConnectionPool ( DbConnection owningObject , DbConnectionPoolGroup connectionPoolGroup ) {
// if poolgroup is disabled, it will be replaced with a new entry
Debug . Assert ( null ! = owningObject , "null owningObject?" ) ;
Debug . Assert ( null ! = connectionPoolGroup , "null connectionPoolGroup?" ) ;
// It is possible that while the outer connection object has
// been sitting around in a closed and unused state in some long
// running app, the pruner may have come along and remove this
// the pool entry from the master list. If we were to use a
// pool entry in this state, we would create "unmanaged" pools,
// which would be bad. To avoid this problem, we automagically
// re-create the pool entry whenever it's disabled.
// however, don't rebuild connectionOptions if no pooling is involved - let new connections do that work
if ( connectionPoolGroup . IsDisabled & & ( null ! = connectionPoolGroup . PoolGroupOptions ) ) {
Bid . Trace ( "<prov.DbConnectionFactory.GetConnectionPool|RES|INFO|CPOOL> %d#, DisabledPoolGroup=%d#\n" , ObjectID , connectionPoolGroup . ObjectID ) ;
// reusing existing pool option in case user originally used SetConnectionPoolOptions
DbConnectionPoolGroupOptions poolOptions = connectionPoolGroup . PoolGroupOptions ;
// get the string to hash on again
DbConnectionOptions connectionOptions = connectionPoolGroup . ConnectionOptions ;
Debug . Assert ( null ! = connectionOptions , "prevent expansion of connectionString" ) ;
connectionPoolGroup = GetConnectionPoolGroup ( connectionPoolGroup . PoolKey , poolOptions , ref connectionOptions ) ;
Debug . Assert ( null ! = connectionPoolGroup , "null connectionPoolGroup?" ) ;
SetConnectionPoolGroup ( owningObject , connectionPoolGroup ) ;
}
DbConnectionPool connectionPool = connectionPoolGroup . GetConnectionPool ( this ) ;
return connectionPool ;
}
internal DbConnectionPoolGroup GetConnectionPoolGroup ( DbConnectionPoolKey key , DbConnectionPoolGroupOptions poolOptions , ref DbConnectionOptions userConnectionOptions ) {
if ( ADP . IsEmpty ( key . ConnectionString ) ) {
return ( DbConnectionPoolGroup ) null ;
}
DbConnectionPoolGroup connectionPoolGroup ;
Dictionary < DbConnectionPoolKey , DbConnectionPoolGroup > connectionPoolGroups = _connectionPoolGroups ;
if ( ! connectionPoolGroups . TryGetValue ( key , out connectionPoolGroup ) | | ( connectionPoolGroup . IsDisabled & & ( null ! = connectionPoolGroup . PoolGroupOptions ) ) ) {
// If we can't find an entry for the connection string in
// our collection of pool entries, then we need to create a
// new pool entry and add it to our collection.
DbConnectionOptions connectionOptions = CreateConnectionOptions ( key . ConnectionString , userConnectionOptions ) ;
if ( null = = connectionOptions ) {
throw ADP . InternalConnectionError ( ADP . ConnectionError . ConnectionOptionsMissing ) ;
}
string expandedConnectionString = key . ConnectionString ;
if ( null = = userConnectionOptions ) { // we only allow one expansion on the connection string
userConnectionOptions = connectionOptions ;
expandedConnectionString = connectionOptions . Expand ( ) ;
// if the expanded string is same instance (default implementation), the use the already created options
if ( ( object ) expandedConnectionString ! = ( object ) key . ConnectionString ) {
//
DbConnectionPoolKey newKey = ( DbConnectionPoolKey ) ( ( ICloneable ) key ) . Clone ( ) ;
newKey . ConnectionString = expandedConnectionString ;
return GetConnectionPoolGroup ( newKey , null , ref userConnectionOptions ) ;
}
}
// We don't support connection pooling on Win9x; it lacks too many of the APIs we require.
if ( ( null = = poolOptions ) & & ADP . IsWindowsNT ) {
if ( null ! = connectionPoolGroup ) {
// reusing existing pool option in case user originally used SetConnectionPoolOptions
poolOptions = connectionPoolGroup . PoolGroupOptions ;
}
else {
// Note: may return null for non-pooled connections
poolOptions = CreateConnectionPoolGroupOptions ( connectionOptions ) ;
}
}
DbConnectionPoolGroup newConnectionPoolGroup = new DbConnectionPoolGroup ( connectionOptions , key , poolOptions ) ;
newConnectionPoolGroup . ProviderInfo = CreateConnectionPoolGroupProviderInfo ( connectionOptions ) ;
lock ( this ) {
connectionPoolGroups = _connectionPoolGroups ;
if ( ! connectionPoolGroups . TryGetValue ( key , out connectionPoolGroup ) ) {
// build new dictionary with space for new connection string
Dictionary < DbConnectionPoolKey , DbConnectionPoolGroup > newConnectionPoolGroups = new Dictionary < DbConnectionPoolKey , DbConnectionPoolGroup > ( 1 + connectionPoolGroups . Count ) ;
foreach ( KeyValuePair < DbConnectionPoolKey , DbConnectionPoolGroup > entry in connectionPoolGroups ) {
newConnectionPoolGroups . Add ( entry . Key , entry . Value ) ;
}
// lock prevents race condition with PruneConnectionPoolGroups
newConnectionPoolGroups . Add ( key , newConnectionPoolGroup ) ;
#if ! MOBILE
PerformanceCounters . NumberOfActiveConnectionPoolGroups . Increment ( ) ;
#endif
connectionPoolGroup = newConnectionPoolGroup ;
_connectionPoolGroups = newConnectionPoolGroups ;
}
else {
Debug . Assert ( ! connectionPoolGroup . IsDisabled , "Disabled pool entry discovered" ) ;
}
}
Debug . Assert ( null ! = connectionPoolGroup , "how did we not create a pool entry?" ) ;
Debug . Assert ( null ! = userConnectionOptions , "how did we not have user connection options?" ) ;
}
else if ( null = = userConnectionOptions ) {
userConnectionOptions = connectionPoolGroup . ConnectionOptions ;
}
return connectionPoolGroup ;
}
internal DbMetaDataFactory GetMetaDataFactory ( DbConnectionPoolGroup connectionPoolGroup , DbConnectionInternal internalConnection ) {
Debug . Assert ( connectionPoolGroup ! = null , "connectionPoolGroup may not be null." ) ;
// get the matadatafactory from the pool entry. If it does not already have one
// create one and save it on the pool entry
DbMetaDataFactory metaDataFactory = connectionPoolGroup . MetaDataFactory ;
// consider serializing this so we don't construct multiple metadata factories
// if two threads happen to hit this at the same time. One will be GC'd
if ( metaDataFactory = = null ) {
bool allowCache = false ;
metaDataFactory = CreateMetaDataFactory ( internalConnection , out allowCache ) ;
if ( allowCache ) {
connectionPoolGroup . MetaDataFactory = metaDataFactory ;
}
}
return metaDataFactory ;
}
private void PruneConnectionPoolGroups ( object state ) {
// when debugging this method, expect multiple threads at the same time
if ( Bid . AdvancedOn ) {
Bid . Trace ( "<prov.DbConnectionFactory.PruneConnectionPoolGroups|RES|INFO|CPOOL> %d#\n" , ObjectID ) ;
}
// First, walk the pool release list and attempt to clear each
// pool, when the pool is finally empty, we dispose of it. If the
// pool isn't empty, it's because there are active connections or
// distributed transactions that need it.
lock ( _poolsToRelease ) {
if ( 0 ! = _poolsToRelease . Count ) {
DbConnectionPool [ ] poolsToRelease = _poolsToRelease . ToArray ( ) ;
foreach ( DbConnectionPool pool in poolsToRelease ) {
if ( null ! = pool ) {
pool . Clear ( ) ;
if ( 0 = = pool . Count ) {
_poolsToRelease . Remove ( pool ) ;
if ( Bid . AdvancedOn ) {
Bid . Trace ( "<prov.DbConnectionFactory.PruneConnectionPoolGroups|RES|INFO|CPOOL> %d#, ReleasePool=%d#\n" , ObjectID , pool . ObjectID ) ;
}
#if ! MOBILE
PerformanceCounters . NumberOfInactiveConnectionPools . Decrement ( ) ;
#endif
}
}
}
}
}
// Next, walk the pool entry release list and dispose of each
// pool entry when it is finally empty. If the pool entry isn't
// empty, it's because there are active pools that need it.
lock ( _poolGroupsToRelease ) {
if ( 0 ! = _poolGroupsToRelease . Count ) {
DbConnectionPoolGroup [ ] poolGroupsToRelease = _poolGroupsToRelease . ToArray ( ) ;
foreach ( DbConnectionPoolGroup poolGroup in poolGroupsToRelease ) {
if ( null ! = poolGroup ) {
int poolsLeft = poolGroup . Clear ( ) ; // may add entries to _poolsToRelease
if ( 0 = = poolsLeft ) {
_poolGroupsToRelease . Remove ( poolGroup ) ;
if ( Bid . AdvancedOn ) {
Bid . Trace ( "<prov.DbConnectionFactory.PruneConnectionPoolGroups|RES|INFO|CPOOL> %d#, ReleasePoolGroup=%d#\n" , ObjectID , poolGroup . ObjectID ) ;
}
#if ! MOBILE
PerformanceCounters . NumberOfInactiveConnectionPoolGroups . Decrement ( ) ;
#endif
}
}
}
}
}
// Finally, we walk through the collection of connection pool entries
// and prune each one. This will cause any empty pools to be put
// into the release list.
lock ( this ) {
Dictionary < DbConnectionPoolKey , DbConnectionPoolGroup > connectionPoolGroups = _connectionPoolGroups ;
Dictionary < DbConnectionPoolKey , DbConnectionPoolGroup > newConnectionPoolGroups = new Dictionary < DbConnectionPoolKey , DbConnectionPoolGroup > ( connectionPoolGroups . Count ) ;
foreach ( KeyValuePair < DbConnectionPoolKey , DbConnectionPoolGroup > entry in connectionPoolGroups ) {
if ( null ! = entry . Value ) {
Debug . Assert ( ! entry . Value . IsDisabled , "Disabled pool entry discovered" ) ;
// entries start active and go idle during prune if all pools are gone
// move idle entries from last prune pass to a queue for pending release
// otherwise process entry which may move it from active to idle
if ( entry . Value . Prune ( ) ) { // may add entries to _poolsToRelease
#if ! MOBILE
PerformanceCounters . NumberOfActiveConnectionPoolGroups . Decrement ( ) ;
#endif
QueuePoolGroupForRelease ( entry . Value ) ;
}
else {
newConnectionPoolGroups . Add ( entry . Key , entry . Value ) ;
}
}
}
_connectionPoolGroups = newConnectionPoolGroups ;
}
}
internal void QueuePoolForRelease ( DbConnectionPool pool , bool clearing ) {
// Queue the pool up for release -- we'll clear it out and dispose
// of it as the last part of the pruning timer callback so we don't
// do it with the pool entry or the pool collection locked.
Debug . Assert ( null ! = pool , "null pool?" ) ;
// set the pool to the shutdown state to force all active
// connections to be automatically disposed when they
// are returned to the pool
pool . Shutdown ( ) ;
lock ( _poolsToRelease ) {
if ( clearing ) {
pool . Clear ( ) ;
}
_poolsToRelease . Add ( pool ) ;
}
#if ! MOBILE
PerformanceCounters . NumberOfInactiveConnectionPools . Increment ( ) ;
#endif
}
internal void QueuePoolGroupForRelease ( DbConnectionPoolGroup poolGroup ) {
Debug . Assert ( null ! = poolGroup , "null poolGroup?" ) ;
Bid . Trace ( "<prov.DbConnectionFactory.QueuePoolGroupForRelease|RES|INFO|CPOOL> %d#, poolGroup=%d#\n" , ObjectID , poolGroup . ObjectID ) ;
lock ( _poolGroupsToRelease ) {
_poolGroupsToRelease . Add ( poolGroup ) ;
}
#if ! MOBILE
PerformanceCounters . NumberOfInactiveConnectionPoolGroups . Increment ( ) ;
#endif
}
virtual protected DbConnectionInternal CreateConnection ( DbConnectionOptions options , DbConnectionPoolKey poolKey , object poolGroupProviderInfo , DbConnectionPool pool , DbConnection owningConnection , DbConnectionOptions userOptions ) {
return CreateConnection ( options , poolKey , poolGroupProviderInfo , pool , owningConnection ) ;
}
abstract protected DbConnectionInternal CreateConnection ( DbConnectionOptions options , DbConnectionPoolKey poolKey , object poolGroupProviderInfo , DbConnectionPool pool , DbConnection owningConnection ) ;
abstract protected DbConnectionOptions CreateConnectionOptions ( string connectionString , DbConnectionOptions previous ) ;
abstract protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions ( DbConnectionOptions options ) ;
abstract internal DbConnectionPoolGroup GetConnectionPoolGroup ( DbConnection connection ) ;
abstract internal DbConnectionInternal GetInnerConnection ( DbConnection connection ) ;
abstract protected int GetObjectId ( DbConnection connection ) ;
abstract internal void PermissionDemand ( DbConnection outerConnection ) ;
abstract internal void SetConnectionPoolGroup ( DbConnection outerConnection , DbConnectionPoolGroup poolGroup ) ;
abstract internal void SetInnerConnectionEvent ( DbConnection owningObject , DbConnectionInternal to ) ;
abstract internal bool SetInnerConnectionFrom ( DbConnection owningObject , DbConnectionInternal to , DbConnectionInternal from ) ;
abstract internal void SetInnerConnectionTo ( DbConnection owningObject , DbConnectionInternal to ) ;
}
}