1 //------------------------------------------------------------------------------
2 // <copyright file="DbConnectionPoolGroup.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">[....]</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;
148 connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Decrement();
149 connectionFactory.QueuePoolForRelease(pool, true);
154 // Finally, return the pool collection count - this may be non-zero if something was added while we were clearing
155 return _poolCollection.Count;
158 internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactory) {
159 // When this method returns null it indicates that the connection
160 // factory should not use pooling.
162 // We don't support connection pooling on Win9x; it lacks too
163 // many of the APIs we require.
164 // PoolGroupOptions will only be null when we're not supposed to pool
166 DbConnectionPool pool = null;
167 if (null != _poolGroupOptions) {
168 Debug.Assert(ADP.IsWindowsNT, "should not be pooling on Win9x");
170 DbConnectionPoolIdentity currentIdentity = DbConnectionPoolIdentity.NoIdentity;
171 if (_poolGroupOptions.PoolByIdentity) {
172 // if we're pooling by identity (because integrated security is
173 // being used for these connections) then we need to go out and
174 // search for the connectionPool that matches the current identity.
176 currentIdentity = DbConnectionPoolIdentity.GetCurrent();
178 // If the current token is restricted in some way, then we must
179 // not attempt to pool these connections.
180 if (currentIdentity.IsRestricted) {
181 currentIdentity = null;
184 if (null != currentIdentity) {
185 if (!_poolCollection.TryGetValue(currentIdentity, out pool)) { // find the pool
186 DbConnectionPoolProviderInfo connectionPoolProviderInfo = connectionFactory.CreateConnectionPoolProviderInfo(this.ConnectionOptions);
188 // optimistically create pool, but its callbacks are delayed until after actual add
189 DbConnectionPool newPool = new DbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo);
192 // Did someone already add it to the list?
193 if (!_poolCollection.TryGetValue(currentIdentity, out pool)) {
194 if (MarkPoolGroupAsActive()) {
195 // If we get here, we know for certain that we there isn't
196 // a pool that matches the current identity, so we have to
197 // add the optimistically created one
198 newPool.Startup(); // must start pool before usage
199 bool addResult = _poolCollection.TryAdd(currentIdentity, newPool);
200 Debug.Assert(addResult, "No other pool with current identity should exist at this point");
201 connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Increment();
206 // else pool entry has been disabled so don't create new pools
207 Debug.Assert(PoolGroupStateDisabled == _state, "state should be disabled");
211 // else found an existing pool to use instead
212 Debug.Assert(PoolGroupStateActive == _state, "state should be active since a pool exists and lock holds");
216 if (null != newPool) {
217 // don't need to call connectionFactory.QueuePoolForRelease(newPool) because
218 // pool callbacks were delayed and no risk of connections being created
222 // the found pool could be in any state
228 // keep the pool entry state active when not pooling
229 MarkPoolGroupAsActive();
235 private bool MarkPoolGroupAsActive() {
236 // when getting a connection, make the entry active if it was idle (but not disabled)
237 // must always lock this before calling
239 if (PoolGroupStateIdle == _state) {
240 _state = PoolGroupStateActive;
241 Bid.Trace("<prov.DbConnectionPoolGroup.ClearInternal|RES|INFO|CPOOL> %d#, Active\n", ObjectID);
243 return (PoolGroupStateActive == _state);
246 internal bool Prune() {
247 // must only call from DbConnectionFactory.PruneConnectionPoolGroups on background timer thread
248 // must lock(DbConnectionFactory._connectionPoolGroups.SyncRoot) before calling ReadyToRemove
249 // to avoid conflict with DbConnectionFactory.CreateConnectionPoolGroup replacing pool entry
251 if (_poolCollection.Count > 0) {
252 var newPoolCollection = new ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool>();
254 foreach (var entry in _poolCollection) {
255 DbConnectionPool pool = entry.Value;
265 // Actually prune the pool if there are no connections in the pool and no errors occurred.
266 // Empty pool during pruning indicates zero or low activity, but
267 // an error state indicates the pool needs to stay around to
268 // throttle new connection attempts.
269 if ((!pool.ErrorOccurred) && (0 == pool.Count)) {
271 // Order is important here. First we remove the pool
272 // from the collection of pools so no one will try
273 // to use it while we're processing and finally we put the
274 // pool into a list of pools to be released when they
275 // are completely empty.
276 DbConnectionFactory connectionFactory = pool.ConnectionFactory;
278 connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Decrement();
279 connectionFactory.QueuePoolForRelease(pool, false);
282 newPoolCollection.TryAdd(entry.Key, entry.Value);
286 _poolCollection = newPoolCollection;
289 // must be pruning thread to change state and no connections
290 // otherwise pruning thread risks making entry disabled soon after user calls ClearPool
291 if (0 == _poolCollection.Count) {
292 if (PoolGroupStateActive == _state) {
293 _state = PoolGroupStateIdle;
294 Bid.Trace("<prov.DbConnectionPoolGroup.ClearInternal|RES|INFO|CPOOL> %d#, Idle\n", ObjectID);
296 else if (PoolGroupStateIdle == _state) {
297 _state = PoolGroupStateDisabled;
298 Bid.Trace("<prov.DbConnectionPoolGroup.ReadyToRemove|RES|INFO|CPOOL> %d#, Disabled\n", ObjectID);
301 return (PoolGroupStateDisabled == _state);