536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
886 lines
42 KiB
C#
886 lines
42 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="DbConnectionInternal.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
// <owner current="true" primary="true">Microsoft</owner>
|
|
// <owner current="true" primary="false">Microsoft</owner>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Data.ProviderBase {
|
|
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Data;
|
|
using System.Data.Common;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Runtime.ConstrainedExecution;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.InteropServices.ComTypes;
|
|
using System.Security;
|
|
using System.Security.Permissions;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using SysTx = System.Transactions;
|
|
|
|
internal abstract class DbConnectionInternal { // V1.1.3300
|
|
|
|
|
|
private static int _objectTypeCount;
|
|
internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount);
|
|
|
|
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 DbConnectionPoolCounters _performanceCounters; // the performance counters we're supposed to update
|
|
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 bool _isInStasis;
|
|
|
|
private DateTime _createTime; // when the connection was created.
|
|
|
|
private SysTx.Transaction _enlistedTransaction; // [usage must be thread-safe] the transaction that we're enlisted in, either manually or automatically
|
|
|
|
// _enlistedTransaction is a clone, so that transaction information can be queried even if the original transaction object is disposed.
|
|
// However, there are times when we need to know if the original transaction object was disposed, so we keep a reference to it here.
|
|
// This field should only be assigned a value at the same time _enlistedTransaction is updated.
|
|
// Also, this reference should not be disposed, since we aren't taking ownership of it.
|
|
private SysTx.Transaction _enlistedTransactionOriginal;
|
|
|
|
#if DEBUG
|
|
private int _activateCount; // debug only counter to verify activate/deactivates are in sync.
|
|
#endif //DEBUG
|
|
|
|
protected DbConnectionInternal() : this(ConnectionState.Open, true, false) { // V1.1.3300
|
|
}
|
|
|
|
// 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 SysTx.Transaction EnlistedTransaction {
|
|
get {
|
|
return _enlistedTransaction;
|
|
}
|
|
set {
|
|
SysTx.Transaction currentEnlistedTransaction = _enlistedTransaction;
|
|
if (((null == currentEnlistedTransaction) && (null != value))
|
|
|| ((null != currentEnlistedTransaction) && !currentEnlistedTransaction.Equals(value))) { // WebData 20000024
|
|
|
|
// Pay attention to the order here:
|
|
// 1) defect from any notifications
|
|
// 2) replace the transaction
|
|
// 3) re-enlist in notifications for the new transaction
|
|
|
|
// SQLBUDT #230558 we need to use a clone of the transaction
|
|
// when we store it, or we'll end up keeping it past the
|
|
// duration of the using block of the TransactionScope
|
|
SysTx.Transaction valueClone = null;
|
|
SysTx.Transaction previousTransactionClone = null;
|
|
try {
|
|
if (null != value) {
|
|
valueClone = value.Clone();
|
|
}
|
|
|
|
// NOTE: rather than take locks around several potential round-
|
|
// trips to the server, and/or virtual function calls, we simply
|
|
// presume that you aren't doing something illegal from multiple
|
|
// threads, and check once we get around to finalizing things
|
|
// inside a lock.
|
|
|
|
lock(this) {
|
|
// NOTE: There is still a race condition here, when we are
|
|
// called from EnlistTransaction (which cannot re-enlist)
|
|
// instead of EnlistDistributedTransaction (which can),
|
|
// however this should have been handled by the outer
|
|
// connection which checks to ensure that it's OK. The
|
|
// only case where we have the race condition is multiple
|
|
// concurrent enlist requests to the same connection, which
|
|
// is a bit out of line with something we should have to
|
|
// support.
|
|
|
|
// enlisted transaction can be nullified in Dispose call without lock
|
|
previousTransactionClone = Interlocked.Exchange(ref _enlistedTransaction, valueClone);
|
|
_enlistedTransactionOriginal = value;
|
|
value = valueClone;
|
|
valueClone = null; // we've stored it, don't dispose it.
|
|
}
|
|
}
|
|
finally {
|
|
// we really need to dispose our clones; they may have
|
|
// native resources and GC may not happen soon enough.
|
|
// VSDevDiv 479564: don't dispose if still holding reference in _enlistedTransaction
|
|
if (null != previousTransactionClone &&
|
|
!Object.ReferenceEquals(previousTransactionClone, _enlistedTransaction)) {
|
|
previousTransactionClone.Dispose();
|
|
}
|
|
if (null != valueClone && !Object.ReferenceEquals(valueClone, _enlistedTransaction)) {
|
|
valueClone.Dispose();
|
|
}
|
|
}
|
|
|
|
// I don't believe that we need to lock to protect the actual
|
|
// enlistment in the transaction; it would only protect us
|
|
// against multiple concurrent calls to enlist, which really
|
|
// isn't supported anyway.
|
|
|
|
if (null != value) {
|
|
if (Bid.IsOn(DbConnectionPool.PoolerTracePoints)) {
|
|
int x = value.GetHashCode();
|
|
Bid.PoolerTrace("<prov.DbConnectionInternal.set_EnlistedTransaction|RES|CPOOL> %d#, Transaction %d#, Enlisting.\n", ObjectID, x);
|
|
}
|
|
TransactionOutcomeEnlist(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get boolean value that indicates whether the enlisted transaction has been disposed.
|
|
/// </summary>
|
|
/// <value>
|
|
/// True if there is an enlisted transaction, and it has been diposed.
|
|
/// False if there is an enlisted transaction that has not been disposed, or if the transaction reference is null.
|
|
/// </value>
|
|
/// <remarks>
|
|
/// This method must be called while holding a lock on the DbConnectionInternal instance.
|
|
/// </remarks>
|
|
protected bool EnlistedTransactionDisposed
|
|
{
|
|
get
|
|
{
|
|
// Until the Transaction.Disposed property is public it is necessary to access a member
|
|
// that throws if the object is disposed to determine if in fact the transaction is disposed.
|
|
try
|
|
{
|
|
bool disposed;
|
|
|
|
SysTx.Transaction currentEnlistedTransactionOriginal = _enlistedTransactionOriginal;
|
|
if (currentEnlistedTransactionOriginal != null)
|
|
{
|
|
disposed = currentEnlistedTransactionOriginal.TransactionInformation == null;
|
|
}
|
|
else
|
|
{
|
|
// Don't expect to get here in the general case,
|
|
// Since this getter is called by CheckEnlistedTransactionBinding
|
|
// after checking for a non-null enlisted transaction (and it does so under lock).
|
|
disposed = false;
|
|
}
|
|
|
|
return disposed;
|
|
}
|
|
catch (ObjectDisposedException)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Is this connection in stasis, waiting for transaction to end before returning to pool?
|
|
internal bool IsTxRootWaitingForTxEnd {
|
|
get {
|
|
return _isInStasis;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get boolean that specifies whether an enlisted transaction can be unbound from
|
|
/// the connection when that transaction completes.
|
|
/// </summary>
|
|
/// <value>
|
|
/// True if the enlisted transaction can be unbound on transaction completion; otherwise false.
|
|
/// </value>
|
|
virtual protected bool UnbindOnTransactionCompletion
|
|
{
|
|
get
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Is this a connection that must be put in stasis (or is already in stasis) pending the end of it's transaction?
|
|
virtual protected internal bool IsNonPoolableTransactionRoot {
|
|
get {
|
|
return false; // if you want to have delegated transactions that are non-poolable, you better override this...
|
|
}
|
|
}
|
|
|
|
virtual internal bool IsTransactionRoot {
|
|
get {
|
|
return false; // if you want to have delegated transactions, you better override this...
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
// Remember how this works (I keep getting confused...)
|
|
//
|
|
// _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 is a serious bug...)
|
|
// _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 (not sure how this could happen...)
|
|
//
|
|
// 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 = !IsTxRootWaitingForTxEnd && (_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);
|
|
}
|
|
}
|
|
|
|
internal int ObjectID {
|
|
get {
|
|
return _objectID;
|
|
}
|
|
}
|
|
|
|
protected internal object Owner {
|
|
// We use a weak reference to the owning object so we can identify when
|
|
// it has been garbage collected without thowing exceptions.
|
|
get {
|
|
return _owningObject.Target;
|
|
}
|
|
}
|
|
|
|
internal DbConnectionPool Pool {
|
|
get {
|
|
return _connectionPool;
|
|
}
|
|
}
|
|
|
|
protected DbConnectionPoolCounters PerformanceCounters {
|
|
get {
|
|
return _performanceCounters;
|
|
}
|
|
}
|
|
|
|
virtual protected bool ReadyToPrepareTransaction {
|
|
get {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
protected internal DbReferenceCollection ReferenceCollection {
|
|
get {
|
|
return _referenceCollection;
|
|
}
|
|
}
|
|
|
|
abstract public string ServerVersion {
|
|
get;
|
|
}
|
|
|
|
// this should be abstract but untill it is added to all the providers virtual will have to do Microsoft
|
|
virtual public string ServerVersionNormalized {
|
|
get{
|
|
throw ADP.NotSupported();
|
|
}
|
|
}
|
|
|
|
public bool ShouldHidePassword {
|
|
get {
|
|
return _hidePassword;
|
|
}
|
|
}
|
|
|
|
public ConnectionState State {
|
|
get {
|
|
return _state;
|
|
}
|
|
}
|
|
|
|
abstract protected void Activate(SysTx.Transaction transaction);
|
|
|
|
internal void ActivateConnection(SysTx.Transaction transaction) {
|
|
// Internal method called from the connection pooler so we don't expose
|
|
// the Activate method publicly.
|
|
|
|
Bid.PoolerTrace("<prov.DbConnectionInternal.ActivateConnection|RES|INFO|CPOOL> %d#, Activating\n", ObjectID);
|
|
#if DEBUG
|
|
int activateCount = Interlocked.Increment(ref _activateCount);
|
|
Debug.Assert(1 == activateCount, "activated multiple times?");
|
|
#endif // DEBUG
|
|
|
|
Activate(transaction);
|
|
|
|
#if !MOBILE
|
|
PerformanceCounters.NumberOfActiveConnections.Increment();
|
|
#endif
|
|
}
|
|
|
|
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(IsolationLevel il);
|
|
|
|
virtual public void ChangeDatabase(string value) {
|
|
throw ADP.MethodNotImplemented("ChangeDatabase");
|
|
}
|
|
|
|
internal virtual void CloseConnection(DbConnection owningObject, DbConnectionFactory connectionFactory) {
|
|
// The implementation here is the implementation required for the
|
|
// "open" internal connections, since our own private "closed"
|
|
// singleton internal connection objects override this method to
|
|
// prevent anything funny from happening (like disposing themselves
|
|
// or putting them into a connection pool)
|
|
//
|
|
// Derived class should override DbConnectionInternal.Deactivate and DbConnectionInternal.Dispose
|
|
// for cleaning up after DbConnection.Close
|
|
// protected override void Deactivate() { // override DbConnectionInternal.Close
|
|
// // do derived class connection deactivation for both pooled & non-pooled connections
|
|
// }
|
|
// public override void Dispose() { // override DbConnectionInternal.Close
|
|
// // do derived class cleanup
|
|
// base.Dispose();
|
|
// }
|
|
//
|
|
// overriding DbConnection.Close is also possible, but must provider for their own synchronization
|
|
// public override void Close() { // override DbConnection.Close
|
|
// base.Close();
|
|
// // do derived class outer connection for both pooled & non-pooled connections
|
|
// // user must do their own synchronization here
|
|
// }
|
|
//
|
|
// if the DbConnectionInternal derived class needs to close the connection it should
|
|
// delegate to the DbConnection if one exists or directly call dispose
|
|
// DbConnection owningObject = (DbConnection)Owner;
|
|
// if (null != owningObject) {
|
|
// owningObject.Close(); // force the closed state on the outer object.
|
|
// }
|
|
// else {
|
|
// Dispose();
|
|
// }
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
// DON'T MESS WITH THIS CODE UNLESS YOU KNOW WHAT YOU'RE DOING!
|
|
////////////////////////////////////////////////////////////////
|
|
Debug.Assert(null != owningObject, "null owningObject");
|
|
Debug.Assert(null != connectionFactory, "null connectionFactory");
|
|
|
|
Bid.PoolerTrace("<prov.DbConnectionInternal.CloseConnection|RES|CPOOL> %d# Closing.\n", ObjectID);
|
|
|
|
// if an exception occurs after the state change but before the try block
|
|
// the connection will be stuck in OpenBusy state. The commented out try-catch
|
|
// block doesn't really help because a ThreadAbort during the finally block
|
|
// would just refert the connection to a bad state.
|
|
// Open->Closed: guarantee internal connection is returned to correct pool
|
|
if (connectionFactory.SetInnerConnectionFrom(owningObject, DbConnectionOpenBusy.SingletonInstance, this)) {
|
|
|
|
// Lock to prevent race condition with cancellation
|
|
lock (this) {
|
|
|
|
object lockToken = ObtainAdditionalLocksForClose();
|
|
try {
|
|
PrepareForCloseConnection();
|
|
|
|
DbConnectionPool connectionPool = Pool;
|
|
|
|
// Detach from enlisted transactions that are no longer active on close
|
|
DetachCurrentTransactionIfEnded();
|
|
|
|
// The singleton closed classes won't have owners and
|
|
// connection pools, and we won't want to put them back
|
|
// into the pool.
|
|
if (null != connectionPool) {
|
|
connectionPool.PutObject(this, owningObject); // PutObject calls Deactivate for us...
|
|
// NOTE: Before we leave the PutObject call, another
|
|
// thread may have already popped the connection from
|
|
// the pool, so don't expect to be able to verify it.
|
|
}
|
|
else {
|
|
Deactivate(); // ensure we de-activate non-pooled connections, or the data readers and transactions may not get cleaned up...
|
|
|
|
#if !MOBILE
|
|
PerformanceCounters.HardDisconnectsPerSecond.Increment();
|
|
#endif
|
|
|
|
// To prevent an endless recursion, we need to clear
|
|
// the owning object before we call dispose so that
|
|
// we can't get here a second time... Ordinarily, I
|
|
// would call setting the owner to null a hack, but
|
|
// this is safe since we're about to dispose the
|
|
// object and it won't have an owner after that for
|
|
// certain.
|
|
_owningObject.Target = null;
|
|
|
|
if (IsTransactionRoot) {
|
|
SetInStasis();
|
|
}
|
|
else {
|
|
#if MONO_PARTIAL_DATA_IMPORT
|
|
Dispose();
|
|
#else
|
|
#if !MOBILE
|
|
PerformanceCounters.NumberOfNonPooledConnections.Decrement();
|
|
#endif
|
|
if (this.GetType() != typeof(System.Data.SqlClient.SqlInternalConnectionSmi))
|
|
{
|
|
Dispose();
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
finally {
|
|
ReleaseAdditionalLocksForClose(lockToken);
|
|
// if a ThreadAbort puts us here then its possible the outer connection will not reference
|
|
// this and this will be orphaned, not reclaimed by object pool until outer connection goes out of scope.
|
|
connectionFactory.SetInnerConnectionEvent(owningObject, DbConnectionClosedPreviouslyOpened.SingletonInstance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual internal void PrepareForReplaceConnection() {
|
|
// By default, there is no preperation required
|
|
}
|
|
|
|
virtual protected void PrepareForCloseConnection() {
|
|
// By default, there is no preperation required
|
|
}
|
|
|
|
virtual protected object ObtainAdditionalLocksForClose() {
|
|
return null; // no additional locks in default implementation
|
|
}
|
|
|
|
virtual protected void ReleaseAdditionalLocksForClose(object 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.
|
|
|
|
Bid.PoolerTrace("<prov.DbConnectionInternal.DeactivateConnection|RES|INFO|CPOOL> %d#, Deactivating\n", ObjectID);
|
|
#if DEBUG
|
|
int activateCount = Interlocked.Decrement(ref _activateCount);
|
|
Debug.Assert(0 == activateCount, "activated multiple times?");
|
|
#endif // DEBUG
|
|
|
|
#if !MOBILE
|
|
if (PerformanceCounters != null) { // Pool.Clear will DestroyObject that will clean performanceCounters before going here
|
|
PerformanceCounters.NumberOfActiveConnections.Decrement();
|
|
}
|
|
#endif
|
|
|
|
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; // WebData 111116
|
|
if ((now.Ticks - _createTime.Ticks) > Pool.LoadBalanceTimeout.Ticks) {
|
|
DoNotPoolThisConnection();
|
|
}
|
|
}
|
|
Deactivate();
|
|
}
|
|
|
|
virtual internal void DelegatedTransactionEnded() {
|
|
// Called by System.Transactions when the delegated transaction has
|
|
// completed. We need to make closed connections that are in stasis
|
|
// available again, or disposed closed/leaked non-pooled connections.
|
|
|
|
// IMPORTANT NOTE: You must have taken a lock on the object before
|
|
// you call this method to prevent race conditions with Clear and
|
|
// ReclaimEmancipatedObjects.
|
|
|
|
Bid.Trace("<prov.DbConnectionInternal.DelegatedTransactionEnded|RES|CPOOL> %d#, Delegated Transaction Completed.\n", ObjectID);
|
|
|
|
if (1 == _pooledCount) {
|
|
// When _pooledCount is 1, it indicates a closed, pooled,
|
|
// connection so it is ready to put back into the pool for
|
|
// general use.
|
|
|
|
TerminateStasis(true);
|
|
|
|
Deactivate(); // call it one more time just in case
|
|
|
|
DbConnectionPool pool = Pool;
|
|
|
|
if (null == pool) {
|
|
throw ADP.InternalError(ADP.InternalErrorCode.PooledObjectWithoutPool); // pooled connection does not have a pool
|
|
}
|
|
pool.PutObjectFromTransactedPool(this);
|
|
}
|
|
else if (-1 == _pooledCount && !_owningObject.IsAlive) {
|
|
// When _pooledCount is -1 and the owning object no longer exists,
|
|
// it indicates a closed (or leaked), non-pooled connection so
|
|
// it is safe to dispose.
|
|
|
|
TerminateStasis(false);
|
|
|
|
Deactivate(); // call it one more time just in case
|
|
|
|
// it's a non-pooled connection, we need to dispose of it
|
|
// once and for all, or the server will have fits about us
|
|
// leaving connections open until the client-side GC kicks
|
|
// in.
|
|
#if !MOBILE
|
|
PerformanceCounters.NumberOfNonPooledConnections.Decrement();
|
|
#endif
|
|
Dispose();
|
|
}
|
|
// When _pooledCount is 0, the connection is a pooled connection
|
|
// that is either open (if the owning object is alive) or leaked (if
|
|
// the owning object is not alive) In either case, we can't muck
|
|
// with the connection here.
|
|
}
|
|
|
|
public virtual void Dispose()
|
|
{
|
|
_connectionPool = null;
|
|
_performanceCounters = null;
|
|
_connectionIsDoomed = true;
|
|
_enlistedTransactionOriginal = null; // should not be disposed
|
|
|
|
// Dispose of the _enlistedTransaction since it is a clone
|
|
// of the original reference.
|
|
// VSDD 780271 - _enlistedTransaction can be changed by another thread (TX end event)
|
|
SysTx.Transaction enlistedTransaction = Interlocked.Exchange(ref _enlistedTransaction, null);
|
|
if (enlistedTransaction != null)
|
|
{
|
|
enlistedTransaction.Dispose();
|
|
}
|
|
}
|
|
|
|
protected internal void DoNotPoolThisConnection() {
|
|
_cannotBePooled = true;
|
|
Bid.PoolerTrace("<prov.DbConnectionInternal.DoNotPoolThisConnection|RES|INFO|CPOOL> %d#, Marking pooled object as non-poolable so it will be disposed\n", ObjectID);
|
|
}
|
|
|
|
/// <devdoc>Ensure that this connection cannot be put back into the pool.</devdoc>
|
|
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
|
|
protected internal void DoomThisConnection() {
|
|
_connectionIsDoomed = true;
|
|
Bid.PoolerTrace("<prov.DbConnectionInternal.DoomThisConnection|RES|INFO|CPOOL> %d#, Dooming\n", ObjectID);
|
|
}
|
|
|
|
abstract public void EnlistTransaction(SysTx.Transaction transaction);
|
|
|
|
virtual protected internal 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, DbConnectionPoolCounters performanceCounters) {
|
|
// Used by DbConnectionFactory to indicate that this object IS NOT part of
|
|
// a connection pool.
|
|
|
|
_connectionPool = null;
|
|
_performanceCounters = performanceCounters;
|
|
_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; // WebData 111116
|
|
|
|
_connectionPool = connectionPool;
|
|
_performanceCounters = connectionPool.PerformanceCounters;
|
|
}
|
|
|
|
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("TryReplaceConnection");
|
|
}
|
|
|
|
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 occure 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
|
|
}
|
|
if (Bid.IsOn(DbConnectionPool.PoolerTracePoints)) {
|
|
//DbConnection x = (expectedOwner as DbConnection);
|
|
Bid.PoolerTrace("<prov.DbConnectionInternal.PrePush|RES|CPOOL> %d#, Preparing to push into pool, owning connection %d#, pooledCount=%d\n", 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");
|
|
|
|
// SQLBUDT #356871 -- 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--;
|
|
if (Bid.IsOn(DbConnectionPool.PoolerTracePoints)) {
|
|
//DbConnection x = (newOwner as DbConnection);
|
|
Bid.PoolerTrace("<prov.DbConnectionInternal.PostPop|RES|CPOOL> %d#, Preparing to pop from pool, owning connection %d#, pooledCount=%d\n", 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);
|
|
}
|
|
}
|
|
|
|
// Cleanup connection's transaction-specific structures (currently used by Delegated transaction).
|
|
// This is a separate method because cleanup can be triggered in multiple ways for a delegated
|
|
// transaction.
|
|
virtual protected void CleanupTransactionOnCompletion(SysTx.Transaction transaction) {
|
|
}
|
|
|
|
internal void DetachCurrentTransactionIfEnded() {
|
|
SysTx.Transaction enlistedTransaction = EnlistedTransaction;
|
|
if (enlistedTransaction != null) {
|
|
bool transactionIsDead;
|
|
try {
|
|
transactionIsDead = (SysTx.TransactionStatus.Active != enlistedTransaction.TransactionInformation.Status);
|
|
}
|
|
catch (SysTx.TransactionException) {
|
|
// If the transaction is being processed (i.e. is part way through a rollback\commit\etc then TransactionInformation.Status will throw an exception)
|
|
transactionIsDead = true;
|
|
}
|
|
if (transactionIsDead) {
|
|
DetachTransaction(enlistedTransaction, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Detach transaction from connection.
|
|
internal void DetachTransaction(SysTx.Transaction transaction, bool isExplicitlyReleasing) {
|
|
Bid.Trace("<prov.DbConnectionInternal.DetachTransaction|RES|CPOOL> %d#, Transaction Completed. (pooledCount=%d)\n", ObjectID, _pooledCount);
|
|
|
|
// potentially a multi-threaded event, so lock the connection to make sure we don't enlist in a new
|
|
// transaction between compare and assignment. No need to short circuit outside of lock, since failed comparisons should
|
|
// be the exception, not the rule.
|
|
lock (this) {
|
|
// Detach if detach-on-end behavior, or if outer connection was closed
|
|
DbConnection owner = (DbConnection)Owner;
|
|
if (isExplicitlyReleasing || UnbindOnTransactionCompletion || null == owner) {
|
|
SysTx.Transaction currentEnlistedTransaction = _enlistedTransaction;
|
|
if (currentEnlistedTransaction != null && transaction.Equals(currentEnlistedTransaction)) {
|
|
|
|
EnlistedTransaction = null;
|
|
|
|
if (IsTxRootWaitingForTxEnd) {
|
|
DelegatedTransactionEnded();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle transaction detach, pool cleanup and other post-transaction cleanup tasks associated with
|
|
internal void CleanupConnectionOnTransactionCompletion(SysTx.Transaction transaction) {
|
|
DetachTransaction(transaction, false);
|
|
|
|
DbConnectionPool pool = Pool;
|
|
if (null != pool) {
|
|
pool.TransactionEnded(transaction, this);
|
|
}
|
|
}
|
|
|
|
void TransactionCompletedEvent(object sender, SysTx.TransactionEventArgs e) {
|
|
SysTx.Transaction transaction = e.Transaction;
|
|
|
|
Bid.Trace("<prov.DbConnectionInternal.TransactionCompletedEvent|RES|CPOOL> %d#, Transaction Completed. (pooledCount=%d)\n", ObjectID, _pooledCount);
|
|
|
|
CleanupTransactionOnCompletion(transaction);
|
|
|
|
CleanupConnectionOnTransactionCompletion(transaction);
|
|
}
|
|
|
|
|
|
//
|
|
[SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode)]
|
|
private void TransactionOutcomeEnlist(SysTx.Transaction transaction) {
|
|
transaction.TransactionCompleted += new SysTx.TransactionCompletedEventHandler(TransactionCompletedEvent);
|
|
}
|
|
|
|
internal void SetInStasis() {
|
|
_isInStasis = true;
|
|
Bid.PoolerTrace("<prov.DbConnectionInternal.SetInStasis|RES|CPOOL> %d#, Non-Pooled Connection has Delegated Transaction, waiting to Dispose.\n", ObjectID);
|
|
#if !MOBILE
|
|
PerformanceCounters.NumberOfStasisConnections.Increment();
|
|
#endif
|
|
}
|
|
|
|
private void TerminateStasis(bool returningToPool) {
|
|
if (returningToPool) {
|
|
Bid.PoolerTrace("<prov.DbConnectionInternal.TerminateStasis|RES|CPOOL> %d#, Delegated Transaction has ended, connection is closed. Returning to general pool.\n", ObjectID);
|
|
}
|
|
else {
|
|
Bid.PoolerTrace("<prov.DbConnectionInternal.TerminateStasis|RES|CPOOL> %d#, Delegated Transaction has ended, connection is closed/leaked. Disposing.\n", ObjectID);
|
|
}
|
|
#if !MOBILE
|
|
PerformanceCounters.NumberOfStasisConnections.Decrement();
|
|
#endif
|
|
_isInStasis = false;
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
}
|