1 //------------------------------------------------------------------------------
2 // <copyright file="DbConnectionPoolGroup.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
8 namespace System.Data.ProviderBase {
11 using System.Collections.Concurrent;
12 using System.Data.Common;
13 using System.Diagnostics;
14 using System.Threading;
16 // set_ConnectionString calls DbConnectionFactory.GetConnectionPoolGroup
17 // when not found a new pool entry is created and potentially added
18 // DbConnectionPoolGroup starts in the Active state
20 // Open calls DbConnectionFactory.GetConnectionPool
21 // if the existing pool entry is Disabled, GetConnectionPoolGroup is called for a new entry
22 // DbConnectionFactory.GetConnectionPool calls DbConnectionPoolGroup.GetConnectionPool
24 // DbConnectionPoolGroup.GetConnectionPool will return pool for the current identity
25 // or null if identity is restricted or pooling is disabled or state is disabled at time of add
26 // state changes are Active->Active, Idle->Active
28 // DbConnectionFactory.PruneConnectionPoolGroups calls Prune
29 // which will QueuePoolForRelease on all empty pools
30 // and once no pools remain, change state from Active->Idle->Disabled
31 // Once Disabled, factory can remove its reference to the pool entry
33 sealed internal class DbConnectionPoolGroup {
34 private readonly DbConnectionOptions _connectionOptions;
35 private readonly DbConnectionPoolKey _poolKey;
36 private readonly DbConnectionPoolGroupOptions _poolGroupOptions;
37 private ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool> _poolCollection;
39 private int _state; // see PoolGroupState* below
41 private DbConnectionPoolGroupProviderInfo _providerInfo;
42 private DbMetaDataFactory _metaDataFactory;
44 private static int _objectTypeCount; // Bid counter
45 internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
47 // always lock this before changing _state, we don't want to move out of the 'Disabled' state
48 // PoolGroupStateUninitialized = 0;
49 private const int PoolGroupStateActive = 1; // initial state, GetPoolGroup from cache, connection Open
50 private const int PoolGroupStateIdle = 2; // all pools are pruned via Clear
51 private const int PoolGroupStateDisabled = 4; // factory pool entry prunning method
53 internal DbConnectionPoolGroup (DbConnectionOptions connectionOptions, DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolGroupOptions) {
54 Debug.Assert(null != connectionOptions, "null connection options");
55 Debug.Assert(null == poolGroupOptions || ADP.IsWindowsNT, "should not have pooling options on Win9x");
57 _connectionOptions = connectionOptions;
59 _poolGroupOptions = poolGroupOptions;
61 // always lock this object before changing state
62 // HybridDictionary does not create any sub-objects until add
63 // so it is safe to use for non-pooled connection as long as
64 // we check _poolGroupOptions first
65 _poolCollection = new ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool>();
66 _state = PoolGroupStateActive; // VSWhidbey 112102
69 internal DbConnectionOptions ConnectionOptions {
71 return _connectionOptions;
75 internal DbConnectionPoolKey PoolKey {
81 internal DbConnectionPoolGroupProviderInfo ProviderInfo {
86 _providerInfo = value;
88 _providerInfo.PoolGroup = this;
93 internal bool IsDisabled {
95 return (PoolGroupStateDisabled == _state);
99 internal int ObjectID {
105 internal DbConnectionPoolGroupOptions PoolGroupOptions {
107 return _poolGroupOptions;
111 internal DbMetaDataFactory MetaDataFactory{
113 return _metaDataFactory;
117 _metaDataFactory = value;
121 internal int Clear() {
122 // must be multi-thread safe with competing calls by Clear and Prune via background thread
123 // will return the number of connections in the group after clearing has finished
125 // First, note the old collection and create a new collection to be used
126 ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool> oldPoolCollection = null;
128 if (_poolCollection.Count > 0) {
129 oldPoolCollection = _poolCollection;
130 _poolCollection = new ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool>();
134 // Then, if a new collection was created, release the pools from the old collection
135 if (oldPoolCollection != null) {
136 foreach (var entry in oldPoolCollection) {
137 DbConnectionPool pool = entry.Value;
147 DbConnectionFactory connectionFactory = pool.ConnectionFactory;
149 connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Decrement();
151 connectionFactory.QueuePoolForRelease(pool, true);
156 // Finally, return the pool collection count - this may be non-zero if something was added while we were clearing
157 return _poolCollection.Count;
160 internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactory) {
161 // When this method returns null it indicates that the connection
162 // factory should not use pooling.
164 // We don't support connection pooling on Win9x; it lacks too
165 // many of the APIs we require.
166 // PoolGroupOptions will only be null when we're not supposed to pool
168 DbConnectionPool pool = null;
169 if (null != _poolGroupOptions) {
170 Debug.Assert(ADP.IsWindowsNT, "should not be pooling on Win9x");
172 DbConnectionPoolIdentity currentIdentity = DbConnectionPoolIdentity.NoIdentity;
173 if (_poolGroupOptions.PoolByIdentity) {
174 // if we're pooling by identity (because integrated security is
175 // being used for these connections) then we need to go out and
176 // search for the connectionPool that matches the current identity.
178 currentIdentity = DbConnectionPoolIdentity.GetCurrent();
180 // If the current token is restricted in some way, then we must
181 // not attempt to pool these connections.
182 if (currentIdentity.IsRestricted) {
183 currentIdentity = null;
186 if (null != currentIdentity) {
187 if (!_poolCollection.TryGetValue(currentIdentity, out pool)) { // find the pool
188 DbConnectionPoolProviderInfo connectionPoolProviderInfo = connectionFactory.CreateConnectionPoolProviderInfo(this.ConnectionOptions);
190 // optimistically create pool, but its callbacks are delayed until after actual add
191 DbConnectionPool newPool = new DbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo);
194 // Did someone already add it to the list?
195 if (!_poolCollection.TryGetValue(currentIdentity, out pool)) {
196 if (MarkPoolGroupAsActive()) {
197 // If we get here, we know for certain that we there isn't
198 // a pool that matches the current identity, so we have to
199 // add the optimistically created one
200 newPool.Startup(); // must start pool before usage
201 bool addResult = _poolCollection.TryAdd(currentIdentity, newPool);
202 Debug.Assert(addResult, "No other pool with current identity should exist at this point");
204 connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Increment();
210 // else pool entry has been disabled so don't create new pools
211 Debug.Assert(PoolGroupStateDisabled == _state, "state should be disabled");
215 // else found an existing pool to use instead
216 Debug.Assert(PoolGroupStateActive == _state, "state should be active since a pool exists and lock holds");
220 if (null != newPool) {
221 // don't need to call connectionFactory.QueuePoolForRelease(newPool) because
222 // pool callbacks were delayed and no risk of connections being created
226 // the found pool could be in any state
232 // keep the pool entry state active when not pooling
233 MarkPoolGroupAsActive();
239 private bool MarkPoolGroupAsActive() {
240 // when getting a connection, make the entry active if it was idle (but not disabled)
241 // must always lock this before calling
243 if (PoolGroupStateIdle == _state) {
244 _state = PoolGroupStateActive;
245 Bid.Trace("<prov.DbConnectionPoolGroup.ClearInternal|RES|INFO|CPOOL> %d#, Active\n", ObjectID);
247 return (PoolGroupStateActive == _state);
250 internal bool Prune() {
251 // must only call from DbConnectionFactory.PruneConnectionPoolGroups on background timer thread
252 // must lock(DbConnectionFactory._connectionPoolGroups.SyncRoot) before calling ReadyToRemove
253 // to avoid conflict with DbConnectionFactory.CreateConnectionPoolGroup replacing pool entry
255 if (_poolCollection.Count > 0) {
256 var newPoolCollection = new ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool>();
258 foreach (var entry in _poolCollection) {
259 DbConnectionPool pool = entry.Value;
269 // Actually prune the pool if there are no connections in the pool and no errors occurred.
270 // Empty pool during pruning indicates zero or low activity, but
271 // an error state indicates the pool needs to stay around to
272 // throttle new connection attempts.
273 if ((!pool.ErrorOccurred) && (0 == pool.Count)) {
275 // Order is important here. First we remove the pool
276 // from the collection of pools so no one will try
277 // to use it while we're processing and finally we put the
278 // pool into a list of pools to be released when they
279 // are completely empty.
280 DbConnectionFactory connectionFactory = pool.ConnectionFactory;
283 connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Decrement();
285 connectionFactory.QueuePoolForRelease(pool, false);
288 newPoolCollection.TryAdd(entry.Key, entry.Value);
292 _poolCollection = newPoolCollection;
295 // must be pruning thread to change state and no connections
296 // otherwise pruning thread risks making entry disabled soon after user calls ClearPool
297 if (0 == _poolCollection.Count) {
298 if (PoolGroupStateActive == _state) {
299 _state = PoolGroupStateIdle;
300 Bid.Trace("<prov.DbConnectionPoolGroup.ClearInternal|RES|INFO|CPOOL> %d#, Idle\n", ObjectID);
302 else if (PoolGroupStateIdle == _state) {
303 _state = PoolGroupStateDisabled;
304 Bid.Trace("<prov.DbConnectionPoolGroup.ReadyToRemove|RES|INFO|CPOOL> %d#, Disabled\n", ObjectID);
307 return (PoolGroupStateDisabled == _state);