Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data / System / Data / ProviderBase / DbConnectionPoolGroup.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="DbConnectionPoolGroup.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
7
8 namespace System.Data.ProviderBase {
9
10     using System;
11     using System.Collections.Concurrent;
12     using System.Data.Common;
13     using System.Diagnostics;
14     using System.Threading;
15
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
19
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
23
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
27
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
32
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;
38
39         private          int                               _state;          // see PoolGroupState* below
40
41         private          DbConnectionPoolGroupProviderInfo _providerInfo;
42         private          DbMetaDataFactory                 _metaDataFactory;
43
44         private static int _objectTypeCount; // Bid counter
45         internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
46
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
52
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");
56
57             _connectionOptions = connectionOptions;
58             _poolKey = key;
59             _poolGroupOptions = poolGroupOptions;
60
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
67         }
68
69         internal DbConnectionOptions ConnectionOptions {
70             get {
71                 return _connectionOptions;
72             }
73         }
74
75         internal DbConnectionPoolKey PoolKey {
76             get {
77                 return _poolKey;
78             }
79         }
80
81         internal DbConnectionPoolGroupProviderInfo ProviderInfo {
82             get {
83                 return _providerInfo;
84             }
85             set {
86                 _providerInfo = value;
87                 if(null!=value) {
88                     _providerInfo.PoolGroup = this;
89                 }
90             }
91         }
92
93         internal bool IsDisabled {
94             get {
95                 return (PoolGroupStateDisabled == _state);
96             }
97         }
98
99         internal int ObjectID {
100             get {
101                 return _objectID;
102             }
103         }
104
105         internal DbConnectionPoolGroupOptions PoolGroupOptions {
106             get {
107                 return _poolGroupOptions;
108             }
109         }
110
111         internal DbMetaDataFactory MetaDataFactory{
112             get {
113                 return  _metaDataFactory;
114                 }
115
116             set {
117                 _metaDataFactory = value;
118             }
119         }
120
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
124
125             // First, note the old collection and create a new collection to be used
126             ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool> oldPoolCollection = null;
127             lock (this) {
128                 if (_poolCollection.Count > 0) {
129                     oldPoolCollection = _poolCollection;
130                     _poolCollection = new ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool>();
131                 }
132             }
133
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;
138                     if (pool != null) {
139                         // 
140
141
142
143
144
145
146
147                         DbConnectionFactory connectionFactory = pool.ConnectionFactory;
148 #if !MOBILE
149                         connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Decrement();
150 #endif
151                         connectionFactory.QueuePoolForRelease(pool, true);
152                     }
153                 }
154             }
155
156             // Finally, return the pool collection count - this may be non-zero if something was added while we were clearing
157             return _poolCollection.Count;
158         }
159
160         internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactory) {
161             // When this method returns null it indicates that the connection
162             // factory should not use pooling.
163
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
167             // connections.
168             DbConnectionPool pool = null;
169             if (null != _poolGroupOptions) {
170                 Debug.Assert(ADP.IsWindowsNT, "should not be pooling on Win9x");
171
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.
177
178                     currentIdentity = DbConnectionPoolIdentity.GetCurrent();
179
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;
184                     }
185                 }
186                 if (null != currentIdentity) {
187                     if (!_poolCollection.TryGetValue(currentIdentity, out pool)) { // find the pool
188                         DbConnectionPoolProviderInfo connectionPoolProviderInfo = connectionFactory.CreateConnectionPoolProviderInfo(this.ConnectionOptions);
189
190                         // optimistically create pool, but its callbacks are delayed until after actual add
191                         DbConnectionPool newPool = new DbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo);
192
193                         lock (this) {
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");
203 #if !MOBILE
204                                     connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Increment();
205 #endif
206                                     pool = newPool;
207                                     newPool = null;
208                                 }
209                                 else {
210                                     // else pool entry has been disabled so don't create new pools
211                                     Debug.Assert(PoolGroupStateDisabled == _state, "state should be disabled");
212                                 }
213                             }
214                             else {
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");
217                             }
218                         }
219
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
223                             newPool.Shutdown();
224                         }
225                     }
226                     // the found pool could be in any state
227                 }
228             }
229
230             if (null == pool) {
231                 lock(this) {
232                     // keep the pool entry state active when not pooling
233                     MarkPoolGroupAsActive();
234                 }
235             }
236             return pool;
237         }
238
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
242
243             if (PoolGroupStateIdle == _state) {
244                 _state = PoolGroupStateActive;
245                 Bid.Trace("<prov.DbConnectionPoolGroup.ClearInternal|RES|INFO|CPOOL> %d#, Active\n", ObjectID);
246             }
247             return (PoolGroupStateActive == _state);
248         }
249
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
254             lock (this) {
255                 if (_poolCollection.Count > 0) {
256                     var newPoolCollection = new ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool>();
257
258                     foreach (var entry in _poolCollection) {
259                         DbConnectionPool pool = entry.Value;
260                         if (pool != null) {
261                             // 
262
263
264
265
266
267
268
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)) {
274
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;
281
282 #if !MOBILE
283                                 connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Decrement();
284 #endif
285                                 connectionFactory.QueuePoolForRelease(pool, false);
286                             }
287                             else {
288                                 newPoolCollection.TryAdd(entry.Key, entry.Value);
289                             }
290                         }
291                     }
292                     _poolCollection = newPoolCollection;
293                 }
294
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);
301                     }
302                     else if (PoolGroupStateIdle == _state) {
303                         _state = PoolGroupStateDisabled;
304                         Bid.Trace("<prov.DbConnectionPoolGroup.ReadyToRemove|RES|INFO|CPOOL> %d#, Disabled\n", ObjectID);
305                     }
306                 }
307                 return (PoolGroupStateDisabled == _state);
308             }
309         }
310     }
311 }