e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
619 lines
32 KiB
C#
619 lines
32 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="DbConnectionFactory.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
// <owner current="true" primary="true">[....]</owner>
|
|
// <owner current="true" primary="false">[....]</owner>
|
|
//------------------------------------------------------------------------------
|
|
|
|
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 {
|
|
if (owningConnection.ForceNewConnection) {
|
|
Debug.Assert(!(oldConnection is DbConnectionClosed), "Force new connection, but there is no old connection");
|
|
connection = connectionPool.ReplaceConnection(owningConnection, userOptions, oldConnection);
|
|
}
|
|
else {
|
|
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);
|
|
}
|
|
}
|