forked from dotnet/SqlClient
-
Notifications
You must be signed in to change notification settings - Fork 0
/
DbConnectionPoolGroup.cs
296 lines (255 loc) · 13.2 KB
/
DbConnectionPoolGroup.cs
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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Data.Common;
using Microsoft.Data.SqlClient;
using System.Collections.Concurrent;
using System.Data.Common;
using System.Diagnostics;
using System.Threading;
namespace Microsoft.Data.ProviderBase
{
// 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 s_objectTypeCount; // EventSource counter
// 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 pruning method
internal DbConnectionPoolGroup(DbConnectionOptions connectionOptions, DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolGroupOptions)
{
Debug.Assert(null != connectionOptions, "null connection options");
_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;
}
internal DbConnectionOptions ConnectionOptions => _connectionOptions;
internal DbConnectionPoolKey PoolKey => _poolKey;
internal DbConnectionPoolGroupProviderInfo ProviderInfo
{
get
{
return _providerInfo;
}
set
{
_providerInfo = value;
if (null != value)
{
_providerInfo.PoolGroup = this;
}
}
}
internal bool IsDisabled => (PoolGroupStateDisabled == _state);
internal int ObjectID { get; } = Interlocked.Increment(ref s_objectTypeCount);
internal DbConnectionPoolGroupOptions PoolGroupOptions => _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;
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;
// PoolGroupOptions will only be null when we're not supposed to pool
// connections.
DbConnectionPool pool = null;
if (null != _poolGroupOptions)
{
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
{
lock (this)
{
// Did someone already add it to the list?
if (!_poolCollection.TryGetValue(currentIdentity, out pool))
{
DbConnectionPoolProviderInfo connectionPoolProviderInfo = connectionFactory.CreateConnectionPoolProviderInfo(this.ConnectionOptions);
DbConnectionPool newPool = new DbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo);
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");
pool = newPool;
}
else
{
// else pool entry has been disabled so don't create new pools
Debug.Assert(PoolGroupStateDisabled == _state, "state should be disabled");
// don't need to call connectionFactory.QueuePoolForRelease(newPool) because
// pool callbacks were delayed and no risk of connections being created
newPool.Shutdown();
}
}
else
{
// else found an existing pool to use instead
Debug.Assert(PoolGroupStateActive == _state, "state should be active since a pool exists and lock holds");
}
}
}
// 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;
SqlClientEventSource.Log.TryTraceEvent("<prov.DbConnectionPoolGroup.ClearInternal|RES|INFO|CPOOL> {0}, Active", 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;
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;
SqlClientEventSource.Log.TryTraceEvent("<prov.DbConnectionPoolGroup.ClearInternal|RES|INFO|CPOOL> {0}, Idle", ObjectID);
}
else if (PoolGroupStateIdle == _state)
{
_state = PoolGroupStateDisabled;
SqlClientEventSource.Log.TryTraceEvent("<prov.DbConnectionPoolGroup.ReadyToRemove|RES|INFO|CPOOL> {0}, Disabled", ObjectID);
}
}
return (PoolGroupStateDisabled == _state);
}
}
}
}