Avoid some windows-only calls in System.Data.
[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">[....]</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                         connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Decrement();
149                         connectionFactory.QueuePoolForRelease(pool, true);
150                     }
151                 }
152             }
153
154             // Finally, return the pool collection count - this may be non-zero if something was added while we were clearing
155             return _poolCollection.Count;
156         }
157
158         internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactory) {
159             // When this method returns null it indicates that the connection
160             // factory should not use pooling.
161
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
165             // connections.
166             DbConnectionPool pool = null;
167             if (null != _poolGroupOptions) {
168                 Debug.Assert(ADP.IsWindowsNT, "should not be pooling on Win9x");
169
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.
175
176                     currentIdentity = DbConnectionPoolIdentity.GetCurrent();
177
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;
182                     }
183                 }
184                 if (null != currentIdentity) {
185                     if (!_poolCollection.TryGetValue(currentIdentity, out pool)) { // find the pool
186                         DbConnectionPoolProviderInfo connectionPoolProviderInfo = connectionFactory.CreateConnectionPoolProviderInfo(this.ConnectionOptions);
187
188                         // optimistically create pool, but its callbacks are delayed until after actual add
189                         DbConnectionPool newPool = new DbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo);
190
191                         lock (this) {
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();
202                                     pool = newPool;
203                                     newPool = null;
204                                 }
205                                 else {
206                                     // else pool entry has been disabled so don't create new pools
207                                     Debug.Assert(PoolGroupStateDisabled == _state, "state should be disabled");
208                                 }
209                             }
210                             else {
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");
213                             }
214                         }
215
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
219                             newPool.Shutdown();
220                         }
221                     }
222                     // the found pool could be in any state
223                 }
224             }
225
226             if (null == pool) {
227                 lock(this) {
228                     // keep the pool entry state active when not pooling
229                     MarkPoolGroupAsActive();
230                 }
231             }
232             return pool;
233         }
234
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
238
239             if (PoolGroupStateIdle == _state) {
240                 _state = PoolGroupStateActive;
241                 Bid.Trace("<prov.DbConnectionPoolGroup.ClearInternal|RES|INFO|CPOOL> %d#, Active\n", ObjectID);
242             }
243             return (PoolGroupStateActive == _state);
244         }
245
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
250             lock (this) {
251                 if (_poolCollection.Count > 0) {
252                     var newPoolCollection = new ConcurrentDictionary<DbConnectionPoolIdentity, DbConnectionPool>();
253
254                     foreach (var entry in _poolCollection) {
255                         DbConnectionPool pool = entry.Value;
256                         if (pool != null) {
257                             // 
258
259
260
261
262
263
264
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)) {
270
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;
277
278                                 connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Decrement();
279                                 connectionFactory.QueuePoolForRelease(pool, false);
280                             }
281                             else {
282                                 newPoolCollection.TryAdd(entry.Key, entry.Value);
283                             }
284                         }
285                     }
286                     _poolCollection = newPoolCollection;
287                 }
288
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);
295                     }
296                     else if (PoolGroupStateIdle == _state) {
297                         _state = PoolGroupStateDisabled;
298                         Bid.Trace("<prov.DbConnectionPoolGroup.ReadyToRemove|RES|INFO|CPOOL> %d#, Disabled\n", ObjectID);
299                     }
300                 }
301                 return (PoolGroupStateDisabled == _state);
302             }
303         }
304     }
305 }