forked from dotnet/SqlClient
-
Notifications
You must be signed in to change notification settings - Fork 0
/
DbConnectionInternal.cs
451 lines (383 loc) · 18 KB
/
DbConnectionInternal.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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
// 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;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;
namespace Microsoft.Data.ProviderBase
{
internal abstract partial class DbConnectionInternal
{
internal static readonly StateChangeEventArgs StateChangeClosed = new StateChangeEventArgs(ConnectionState.Open, ConnectionState.Closed);
internal static readonly StateChangeEventArgs StateChangeOpen = new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open);
private readonly bool _allowSetConnectionString;
private readonly bool _hidePassword;
private readonly ConnectionState _state;
private readonly WeakReference _owningObject = new WeakReference(null, false); // [usage must be thread safe] the owning object, when not in the pool. (both Pooled and Non-Pooled connections)
private DbConnectionPool _connectionPool; // the pooler that the connection came from (Pooled connections only)
private DbReferenceCollection _referenceCollection; // collection of objects that we need to notify in some way when we're being deactivated
private int _pooledCount; // [usage must be thread safe] the number of times this object has been pushed into the pool less the number of times it's been popped (0 != inPool)
private bool _connectionIsDoomed; // true when the connection should no longer be used.
private bool _cannotBePooled; // true when the connection should no longer be pooled.
private DateTime _createTime; // when the connection was created.
#if DEBUG
private int _activateCount; // debug only counter to verify activate/deactivates are in sync.
#endif //DEBUG
protected DbConnectionInternal() : this(ConnectionState.Open, true, false)
{
}
// Constructor for internal connections
internal DbConnectionInternal(ConnectionState state, bool hidePassword, bool allowSetConnectionString)
{
_allowSetConnectionString = allowSetConnectionString;
_hidePassword = hidePassword;
_state = state;
}
internal bool AllowSetConnectionString
{
get
{
return _allowSetConnectionString;
}
}
internal bool CanBePooled
{
get
{
bool flag = (!_connectionIsDoomed && !_cannotBePooled && !_owningObject.IsAlive);
return flag;
}
}
protected internal bool IsConnectionDoomed
{
get
{
return _connectionIsDoomed;
}
}
internal bool IsEmancipated
{
get
{
// NOTE: There are race conditions between PrePush, PostPop and this
// property getter -- only use this while this object is locked;
// (DbConnectionPool.Clear and ReclaimEmancipatedObjects
// do this for us)
// The functionality is as follows:
//
// _pooledCount is incremented when the connection is pushed into the pool
// _pooledCount is decremented when the connection is popped from the pool
// _pooledCount is set to -1 when the connection is not pooled (just in case...)
//
// That means that:
//
// _pooledCount > 1 connection is in the pool multiple times (This should not happen)
// _pooledCount == 1 connection is in the pool
// _pooledCount == 0 connection is out of the pool
// _pooledCount == -1 connection is not a pooled connection; we shouldn't be here for non-pooled connections.
// _pooledCount < -1 connection out of the pool multiple times
//
// Now, our job is to return TRUE when the connection is out
// of the pool and it's owning object is no longer around to
// return it.
bool value = (_pooledCount < 1) && !_owningObject.IsAlive;
return value;
}
}
internal bool IsInPool
{
get
{
Debug.Assert(_pooledCount <= 1 && _pooledCount >= -1, "Pooled count for object is invalid");
return (_pooledCount == 1);
}
}
protected internal object Owner
{
// We use a weak reference to the owning object so we can identify when
// it has been garbage collected without throwing exceptions.
get
{
return _owningObject.Target;
}
}
internal DbConnectionPool Pool
{
get
{
return _connectionPool;
}
}
protected internal DbReferenceCollection ReferenceCollection
{
get
{
return _referenceCollection;
}
}
abstract public string ServerVersion
{
get;
}
// this should be abstract but until it is added to all the providers virtual will have to do
virtual public string ServerVersionNormalized
{
get
{
throw ADP.NotSupported();
}
}
public bool ShouldHidePassword
{
get
{
return _hidePassword;
}
}
public ConnectionState State
{
get
{
return _state;
}
}
internal void AddWeakReference(object value, int tag)
{
if (null == _referenceCollection)
{
_referenceCollection = CreateReferenceCollection();
if (null == _referenceCollection)
{
throw ADP.InternalError(ADP.InternalErrorCode.CreateReferenceCollectionReturnedNull);
}
}
_referenceCollection.Add(value, tag);
}
abstract public DbTransaction BeginTransaction(System.Data.IsolationLevel il);
virtual public void ChangeDatabase(string value)
{
throw ADP.MethodNotImplemented();
}
virtual internal void PrepareForReplaceConnection()
{
// By default, there is no preparation required
}
virtual protected void PrepareForCloseConnection()
{
// By default, there is no preparation required
}
virtual protected bool ObtainAdditionalLocksForClose()
{
return false; // no additional locks in default implementation
}
virtual protected void ReleaseAdditionalLocksForClose(bool lockToken)
{
// no additional locks in default implementation
}
virtual protected DbReferenceCollection CreateReferenceCollection()
{
throw ADP.InternalError(ADP.InternalErrorCode.AttemptingToConstructReferenceCollectionOnStaticObject);
}
abstract protected void Deactivate();
internal void DeactivateConnection()
{
// Internal method called from the connection pooler so we don't expose
// the Deactivate method publicly.
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionInternal.DeactivateConnection|RES|INFO|CPOOL> {0}, Deactivating", ObjectID);
#if DEBUG
int activateCount = Interlocked.Decrement(ref _activateCount);
#endif // DEBUG
if (!_connectionIsDoomed && Pool.UseLoadBalancing)
{
// If we're not already doomed, check the connection's lifetime and
// doom it if it's lifetime has elapsed.
DateTime now = DateTime.UtcNow;
if ((now.Ticks - _createTime.Ticks) > Pool.LoadBalanceTimeout.Ticks)
{
DoNotPoolThisConnection();
}
}
Deactivate();
}
protected internal void DoNotPoolThisConnection()
{
_cannotBePooled = true;
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionInternal.DoNotPoolThisConnection|RES|INFO|CPOOL> {0}, Marking pooled object as non-poolable so it will be disposed", ObjectID);
}
/// <devdoc>Ensure that this connection cannot be put back into the pool.</devdoc>
protected internal void DoomThisConnection()
{
_connectionIsDoomed = true;
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionInternal.DoomThisConnection|RES|INFO|CPOOL> {0}, Dooming", ObjectID);
}
// Reset connection doomed status so it can be re-connected and pooled.
protected internal void UnDoomThisConnection()
{
_connectionIsDoomed = false;
}
protected internal virtual DataTable GetSchema(DbConnectionFactory factory, DbConnectionPoolGroup poolGroup, DbConnection outerConnection, string collectionName, string[] restrictions)
{
Debug.Assert(outerConnection != null, "outerConnection may not be null.");
DbMetaDataFactory metaDataFactory = factory.GetMetaDataFactory(poolGroup, this);
Debug.Assert(metaDataFactory != null, "metaDataFactory may not be null.");
return metaDataFactory.GetSchema(outerConnection, collectionName, restrictions);
}
internal void MakeNonPooledObject(object owningObject)
{
// Used by DbConnectionFactory to indicate that this object IS NOT part of
// a connection pool.
_connectionPool = null;
_owningObject.Target = owningObject;
_pooledCount = -1;
}
internal void MakePooledConnection(DbConnectionPool connectionPool)
{
// Used by DbConnectionFactory to indicate that this object IS part of
// a connection pool.
_createTime = DateTime.UtcNow;
_connectionPool = connectionPool;
}
internal void NotifyWeakReference(int message)
{
DbReferenceCollection referenceCollection = ReferenceCollection;
if (null != referenceCollection)
{
referenceCollection.Notify(message);
}
}
internal virtual void OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
{
if (!TryOpenConnection(outerConnection, connectionFactory, null, null))
{
throw ADP.InternalError(ADP.InternalErrorCode.SynchronousConnectReturnedPending);
}
}
/// <devdoc>The default implementation is for the open connection objects, and
/// it simply throws. Our private closed-state connection objects
/// override this and do the correct thing.</devdoc>
// User code should either override DbConnectionInternal.Activate when it comes out of the pool
// or override DbConnectionFactory.CreateConnection when the connection is created for non-pooled connections
internal virtual bool TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions)
{
throw ADP.ConnectionAlreadyOpen(State);
}
internal virtual bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions)
{
throw ADP.MethodNotImplemented();
}
protected bool TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions)
{
// ?->Connecting: prevent set_ConnectionString during Open
if (connectionFactory.SetInnerConnectionFrom(outerConnection, DbConnectionClosedConnecting.SingletonInstance, this))
{
DbConnectionInternal openConnection = null;
try
{
connectionFactory.PermissionDemand(outerConnection);
if (!connectionFactory.TryGetConnection(outerConnection, retry, userOptions, this, out openConnection))
{
return false;
}
}
catch
{
// This should occur for all exceptions, even ADP.UnCatchableExceptions.
connectionFactory.SetInnerConnectionTo(outerConnection, this);
throw;
}
if (null == openConnection)
{
connectionFactory.SetInnerConnectionTo(outerConnection, this);
throw ADP.InternalConnectionError(ADP.ConnectionError.GetConnectionReturnsNull);
}
connectionFactory.SetInnerConnectionEvent(outerConnection, openConnection);
}
return true;
}
internal void PrePush(object expectedOwner)
{
// Called by DbConnectionPool when we're about to be put into it's pool, we
// take this opportunity to ensure ownership and pool counts are legit.
// IMPORTANT NOTE: You must have taken a lock on the object before
// you call this method to prevent race conditions with Clear and
// ReclaimEmancipatedObjects.
//3 // The following tests are retail assertions of things we can't allow to happen.
if (null == expectedOwner)
{
if (null != _owningObject.Target)
{
throw ADP.InternalError(ADP.InternalErrorCode.UnpooledObjectHasOwner); // new unpooled object has an owner
}
}
else if (_owningObject.Target != expectedOwner)
{
throw ADP.InternalError(ADP.InternalErrorCode.UnpooledObjectHasWrongOwner); // unpooled object has incorrect owner
}
if (0 != _pooledCount)
{
throw ADP.InternalError(ADP.InternalErrorCode.PushingObjectSecondTime); // pushing object onto stack a second time
}
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionInternal.PrePush|RES|CPOOL> {0}, Preparing to push into pool, owning connection {1}, pooledCount={2}", ObjectID, 0, _pooledCount);
_pooledCount++;
_owningObject.Target = null; // NOTE: doing this and checking for InternalError.PooledObjectHasOwner degrades the close by 2%
}
internal void PostPop(object newOwner)
{
// Called by DbConnectionPool right after it pulls this from it's pool, we
// take this opportunity to ensure ownership and pool counts are legit.
Debug.Assert(!IsEmancipated, "pooled object not in pool");
// When another thread is clearing this pool, it
// will doom all connections in this pool without prejudice which
// causes the following assert to fire, which really mucks up stress
// against checked bits. The assert is benign, so we're commenting
// it out.
//Debug.Assert(CanBePooled, "pooled object is not poolable");
// IMPORTANT NOTE: You must have taken a lock on the object before
// you call this method to prevent race conditions with Clear and
// ReclaimEmancipatedObjects.
if (null != _owningObject.Target)
{
throw ADP.InternalError(ADP.InternalErrorCode.PooledObjectHasOwner); // pooled connection already has an owner!
}
_owningObject.Target = newOwner;
_pooledCount--;
SqlClientEventSource.Log.TryPoolerTraceEvent("<prov.DbConnectionInternal.PostPop|RES|CPOOL> {0}, Preparing to pop from pool, owning connection {1}, pooledCount={2}", ObjectID, 0, _pooledCount);
//3 // The following tests are retail assertions of things we can't allow to happen.
if (null != Pool)
{
if (0 != _pooledCount)
{
throw ADP.InternalError(ADP.InternalErrorCode.PooledObjectInPoolMoreThanOnce); // popping object off stack with multiple pooledCount
}
}
else if (-1 != _pooledCount)
{
throw ADP.InternalError(ADP.InternalErrorCode.NonPooledObjectUsedMoreThanOnce); // popping object off stack with multiple pooledCount
}
}
internal void RemoveWeakReference(object value)
{
DbReferenceCollection referenceCollection = ReferenceCollection;
if (null != referenceCollection)
{
referenceCollection.Remove(value);
}
}
/// <summary>
/// When overridden in a derived class, will check if the underlying connection is still actually alive
/// </summary>
/// <param name="throwOnException">If true an exception will be thrown if the connection is dead instead of returning true\false
/// (this allows the caller to have the real reason that the connection is not alive (e.g. network error, etc))</param>
/// <returns>True if the connection is still alive, otherwise false (If not overridden, then always true)</returns>
internal virtual bool IsConnectionAlive(bool throwOnException = false)
{
return true;
}
}
}