|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Data.Common;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace System.Data.ProviderBase
{
internal abstract partial class DbConnectionInternal
{
protected abstract void Activate();
internal void ActivateConnection()
{
// Internal method called from the connection pooler so we don't expose
// the Activate method publicly.
#if DEBUG
int activateCount = Interlocked.Increment(ref _activateCount);
Debug.Assert(1 == activateCount, "activated multiple times?");
#endif // DEBUG
Activate();
}
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");
// 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 revert 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;
// 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...
// 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;
Dispose();
}
}
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);
}
}
}
}
public virtual void Dispose()
{
_connectionPool = null;
_connectionIsDoomed = true;
}
}
}
|