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