|
using System;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Threading;
using System.Transactions;
using System.Transactions.Diagnostics;
namespace System.Transactions.Oletx
{
internal abstract class OletxVolatileEnlistmentContainer
{
protected RealOletxTransaction realOletxTransaction;
protected ArrayList enlistmentList;
protected int phase;
protected int outstandingNotifications;
protected bool collectedVoteYes;
protected int incompleteDependentClones;
protected bool alreadyVoted;
internal abstract void DecrementOutstandingNotifications( bool voteYes );
internal abstract void AddDependentClone();
internal abstract void DependentCloneCompleted();
internal abstract void RollbackFromTransaction();
internal abstract void OutcomeFromTransaction( TransactionStatus outcome );
internal abstract void Committed();
internal abstract void Aborted();
internal abstract void InDoubt();
internal Guid TransactionIdentifier
{
get
{
return this.realOletxTransaction.Identifier;
}
}
}
internal class OletxPhase0VolatileEnlistmentContainer : OletxVolatileEnlistmentContainer
{
IPhase0EnlistmentShim phase0EnlistmentShim;
bool aborting;
bool tmWentDown;
internal OletxPhase0VolatileEnlistmentContainer(
RealOletxTransaction realOletxTransaction
)
{
Debug.Assert( null != realOletxTransaction, "Argument is null" );
// This will be set later, after the caller creates the enlistment with the proxy.
this.phase0EnlistmentShim = null;
this.realOletxTransaction = realOletxTransaction;
this.phase = -1;
this.aborting = false;
this.tmWentDown = false;
this.outstandingNotifications = 0;
this.incompleteDependentClones = 0;
this.alreadyVoted = false;
// If anybody votes false, this will get set to false.
this.collectedVoteYes = true;
this.enlistmentList = new ArrayList();
// This is a new undecided enlistment on the transaction. Do this last since it has side affects.
realOletxTransaction.IncrementUndecidedEnlistments();
}
internal void TMDown()
{
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxPhase0VolatileEnlistmentContainer.TMDown"
);
}
this.tmWentDown = true;
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxPhase0VolatileEnlistmentContainer.TMDown"
);
}
}
internal bool NewEnlistmentsAllowed
// Be sure to lock this object before calling this.
{
get
{
return ( -1 == phase );
}
}
internal void AddEnlistment(
OletxVolatileEnlistment enlistment
)
{
Debug.Assert( null != enlistment, "Argument is null" );
lock ( this )
{
if ( -1 != phase )
{
throw TransactionException.Create( SR.GetString( SR.TraceSourceOletx ),
SR.GetString( SR.TooLate ), null );
}
this.enlistmentList.Add( enlistment );
}
}
internal override void AddDependentClone()
{
lock ( this )
{
if ( -1 != phase )
{
throw TransactionException.CreateTransactionStateException( SR.GetString( SR.TraceSourceOletx ), null );
}
this.incompleteDependentClones++;
}
}
internal override void DependentCloneCompleted()
{
bool doDecrement = false;
lock ( this )
{
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase0VolatileEnlistmentContainer.DependentCloneCompleted, outstandingNotifications = " +
this.outstandingNotifications.ToString( CultureInfo.CurrentCulture ) +
", incompleteDependentClones = " +
this.incompleteDependentClones.ToString( CultureInfo.CurrentCulture ) +
", phase = " + this.phase.ToString( CultureInfo.CurrentCulture );
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
this.incompleteDependentClones--;
Debug.Assert( 0 <= this.incompleteDependentClones, "OletxPhase0VolatileEnlistmentContainer.DependentCloneCompleted - incompleteDependentClones < 0" );
// If we have not more incomplete dependent clones and we are in Phase 0, we need to "fake out" a notification completion.
if ( ( 0 == this.incompleteDependentClones ) && ( 0 == this.phase ) )
{
this.outstandingNotifications++;
doDecrement = true;
}
}
if ( doDecrement )
{
DecrementOutstandingNotifications( true );
}
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase0VolatileEnlistmentContainer.DependentCloneCompleted";
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
}
internal override void RollbackFromTransaction()
{
lock ( this )
{
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase0VolatileEnlistmentContainer.RollbackFromTransaction, outstandingNotifications = " +
this.outstandingNotifications.ToString( CultureInfo.CurrentCulture ) +
", incompleteDependentClones = " + this.incompleteDependentClones.ToString( CultureInfo.CurrentCulture );
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
if ( ( 0 == phase ) && ( ( 0 < this.outstandingNotifications ) || ( 0 < incompleteDependentClones ) ) )
{
this.alreadyVoted = true;
// All we are going to do is release the Phase0Enlistment interface because there
// is no negative vote to Phase0Request.
if ( null != this.Phase0EnlistmentShim )
{
this.Phase0EnlistmentShim.Phase0Done( false );
}
}
}
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase0VolatileEnlistmentContainer.RollbackFromTransaction";
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
}
internal IPhase0EnlistmentShim Phase0EnlistmentShim
{
get
{
IPhase0EnlistmentShim returnValue = null;
lock ( this )
{
returnValue = this.phase0EnlistmentShim;
}
return returnValue;
}
set
{
lock ( this )
{
// If this.aborting is set to true, then we must have already received a
// Phase0Request. This could happen if the transaction aborts after the
// enlistment is made, but before we are given the shim.
if ( this.aborting || this.tmWentDown )
{
value.Phase0Done( false );
}
this.phase0EnlistmentShim = value;
}
}
}
internal override void DecrementOutstandingNotifications( bool voteYes )
{
bool respondToProxy = false;
IPhase0EnlistmentShim localPhase0Shim = null;
lock ( this )
{
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase0VolatileEnlistmentContainer.DecrementOutstandingNotifications, outstandingNotifications = " +
this.outstandingNotifications.ToString( CultureInfo.CurrentCulture ) +
", incompleteDependentClones = " +
this.incompleteDependentClones.ToString( CultureInfo.CurrentCulture );
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
outstandingNotifications--;
Debug.Assert( 0 <= outstandingNotifications, "OletxPhase0VolatileEnlistmentContainer.DecrementOutstandingNotifications - outstandingNotifications < 0" );
this.collectedVoteYes = this.collectedVoteYes && voteYes;
if ( ( 0 == this.outstandingNotifications ) && ( 0 == this.incompleteDependentClones ) )
{
if ( ( 0 == this.phase ) && ( !this.alreadyVoted ) )
{
respondToProxy = true;
this.alreadyVoted = true;
localPhase0Shim = this.phase0EnlistmentShim;
}
this.realOletxTransaction.DecrementUndecidedEnlistments();
}
}
try
{
if ( respondToProxy )
{
if ( null != localPhase0Shim )
{
localPhase0Shim.Phase0Done( ( this.collectedVoteYes ) && ( !this.realOletxTransaction.Doomed ) );
}
}
}
catch ( COMException ex )
{
if ( ( NativeMethods.XACT_E_CONNECTION_DOWN == ex.ErrorCode ) ||
( NativeMethods.XACT_E_TMNOTAVAILABLE == ex.ErrorCode )
)
{
if ( DiagnosticTrace.Verbose )
{
ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
ex );
}
}
// In the case of Phase0, there is a bug in the proxy that causes an XACT_E_PROTOCOL
// error if the TM goes down while the enlistment is still active. The Phase0Request is
// sent out with abortHint false, but the state of the proxy object is not changed, causing
// Phase0Done request to fail with XACT_E_PROTOCOL.
// For Prepared, we want to make sure the proxy aborts the transaction. We don't need
// to drive the abort to the application here because the Phase1 enlistment will do that.
// In other words, treat this as if the proxy said Phase0Request( abortingHint = true ).
else if ( NativeMethods.XACT_E_PROTOCOL == ex.ErrorCode )
{
this.phase0EnlistmentShim = null;
if ( DiagnosticTrace.Verbose )
{
ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
ex );
}
}
else
{
throw;
}
}
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase0VolatileEnlistmentContainer.DecrementOutstandingNotifications";
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
}
internal override void OutcomeFromTransaction( TransactionStatus outcome )
{
if ( TransactionStatus.Committed == outcome )
{
this.Committed();
}
else if ( TransactionStatus.Aborted == outcome )
{
this.Aborted();
}
else if ( TransactionStatus.InDoubt == outcome )
{
this.InDoubt();
}
else
{
Debug.Assert( false, "OletxPhase0VolatileEnlistmentContainer.OutcomeFromTransaction, outcome is not Commited or Aborted or InDoubt" );
}
}
internal override void Committed()
{
OletxVolatileEnlistment enlistment = null;
int localCount = 0;
lock ( this )
{
Debug.Assert( ( 0 == phase ) && ( 0 == this.outstandingNotifications ) );
phase = 2;
localCount = this.enlistmentList.Count;
}
for ( int i = 0; i < localCount; i++ )
{
enlistment = this.enlistmentList[i] as OletxVolatileEnlistment;
if ( null == enlistment )
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxPhase1VolatileEnlistmentContainer.Committed, enlistmentList element is not an OletxVolatileEnlistment." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
enlistment.Commit();
}
}
internal override void Aborted()
{
OletxVolatileEnlistment enlistment = null;
int localCount = 0;
lock ( this )
{
// Tell all the enlistments that the transaction aborted and let the enlistment
// state determine if the notification should be delivered.
this.phase = 2;
localCount = this.enlistmentList.Count;
}
for ( int i = 0; i < localCount; i++ )
{
enlistment = this.enlistmentList[i] as OletxVolatileEnlistment;
if ( null == enlistment )
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxPhase1VolatileEnlistmentContainer.Aborted, enlistmentList element is not an OletxVolatileEnlistment." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
enlistment.Rollback();
}
}
internal override void InDoubt()
{
OletxVolatileEnlistment enlistment = null;
int localCount = 0;
lock ( this )
{
// Tell all the enlistments that the transaction is InDoubt and let the enlistment
// state determine if the notification should be delivered.
phase = 2;
localCount = this.enlistmentList.Count;
}
for ( int i = 0; i < localCount; i++ )
{
enlistment = this.enlistmentList[i] as OletxVolatileEnlistment;
if ( null == enlistment )
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxPhase1VolatileEnlistmentContainer.InDoubt, enlistmentList element is not an OletxVolatileEnlistment." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
enlistment.InDoubt();
}
}
internal void Phase0Request(
bool abortHint
)
{
OletxVolatileEnlistment enlistment = null;
int localCount = 0;
OletxCommittableTransaction committableTx = null;
bool commitNotYetCalled = false;
lock ( this )
{
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase0VolatileEnlistmentContainer.Phase0Request, abortHint = " +
abortHint.ToString( CultureInfo.CurrentCulture ) +
", phase = " + this.phase.ToString( CultureInfo.CurrentCulture );
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
this.aborting = abortHint;
committableTx = this.realOletxTransaction.committableTransaction;
if ( null != committableTx )
{
// We are dealing with the committable transaction. If Commit or BeginCommit has NOT been
// called, then we are dealing with a situation where the TM went down and we are getting
// a bogus Phase0Request with abortHint = false (COMPlus bug 36760/36758). This is an attempt
// to not send the app a Prepare request when we know the transaction is going to abort.
if (!committableTx.CommitCalled )
{
commitNotYetCalled = true;
this.aborting = true;
}
}
// It's possible that we are in phase 2 if we got an Aborted outcome from the transaction before we got the
// Phase0Request. In both cases, we just respond to the proxy and don't bother telling the enlistments.
// They have either already heard about the abort or will soon.
if ( ( 2 == this.phase ) || ( -1 == this.phase ) )
{
if ( -1 == this.phase )
{
this.phase = 0;
}
// If we got an abort hint or we are the committable transaction and Commit has not yet been called or the TM went down,
// we don't want to do any more work on the transaction. The abort notifications will be sent by the phase 1
// enlistment
if ( ( this.aborting ) || ( this.tmWentDown ) || ( commitNotYetCalled ) || ( 2 == this.phase ) )
{
// There is a possible ---- where we could get the Phase0Request before we are given the
// shim. In that case, we will vote "no" when we are given the shim.
if ( null != this.phase0EnlistmentShim )
{
try
{
this.phase0EnlistmentShim.Phase0Done( false );
// CSDMain 138031: There is a potential ---- between DTC sending Abort notification and OletxDependentTransaction::Complete is called.
// We need to set the alreadyVoted flag to true once we successfully voted, so later we don't vote again when OletxDependentTransaction::Complete is called
// Otherwise, in OletxPhase0VolatileEnlistmentContainer::DecrementOutstandingNotifications code path, we are going to call Phase0Done( true ) again
// and result in an access violation while accessing the pPhase0EnlistmentAsync member variable of the Phase0Shim object.
this.alreadyVoted = true;
}
// I am not going to check for XACT_E_PROTOCOL here because that check is a workaround for a bug
// that only shows up if abortingHint is false.
catch ( COMException ex )
{
if ( DiagnosticTrace.Verbose )
{
ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
ex );
}
}
}
return;
}
outstandingNotifications = enlistmentList.Count;
localCount = enlistmentList.Count;
// If we don't have any enlistments, then we must have created this container for
// delay commit dependent clones only. So we need to fake a notification.
if ( 0 == localCount )
{
this.outstandingNotifications = 1;
}
}
else // any other phase is bad news.
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxPhase0VolatileEnlistmentContainer.Phase0Request, phase != -1"
);
}
Debug.Assert( false, "OletxPhase0VolatileEnlistmentContainer.Phase0Request, phase != -1" );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
}
// We may not have any Phase0 volatile enlistments, which means that this container
// got created solely for delay commit dependent transactions. We need to fake out a
// notification completion.
if ( 0 == localCount )
{
DecrementOutstandingNotifications( true );
}
else
{
for ( int i = 0; i < localCount; i++ )
{
enlistment = enlistmentList[i] as OletxVolatileEnlistment;
if ( null == enlistment )
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxPhase0VolatileEnlistmentContainer.Phase0Request, enlistmentList element is not an OletxVolatileEnlistment.");
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
// Do the notification outside any locks.
Debug.Assert( enlistment.enlistDuringPrepareRequired, "OletxPhase0VolatileEnlistmentContainer.Phase0Request, enlistmentList element not marked as EnlistmentDuringPrepareRequired." );
Debug.Assert( !abortHint, "OletxPhase0VolatileEnlistmentContainer.Phase0Request, abortingHint is true just before sending Prepares." );
enlistment.Prepare( this );
}
}
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase0VolatileEnlistmentContainer.Phase0Request, abortHint = " + abortHint.ToString( CultureInfo.CurrentCulture );
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
return;
}
}
internal class OletxPhase1VolatileEnlistmentContainer : OletxVolatileEnlistmentContainer
{
IVoterBallotShim voterBallotShim;
internal IntPtr voterHandle = IntPtr.Zero;
internal OletxPhase1VolatileEnlistmentContainer(
RealOletxTransaction realOletxTransaction
)
{
Debug.Assert( null != realOletxTransaction, "Argument is null" );
// This will be set later, after the caller creates the enlistment with the proxy.
voterBallotShim = null;
this.realOletxTransaction = realOletxTransaction;
this.phase = -1;
this.outstandingNotifications = 0;
this.incompleteDependentClones = 0;
this.alreadyVoted = false;
// If anybody votes false, this will get set to false.
this.collectedVoteYes = true;
this.enlistmentList = new ArrayList();
// This is a new undecided enlistment on the transaction. Do this last since it has side affects.
realOletxTransaction.IncrementUndecidedEnlistments();
}
// Returns true if this container is enlisted for Phase 0.
internal void AddEnlistment(
OletxVolatileEnlistment enlistment
)
{
Debug.Assert( null != enlistment, "Argument is null" );
lock ( this )
{
if ( -1 != phase )
{
throw TransactionException.Create( SR.GetString( SR.TraceSourceOletx ),
SR.GetString( SR.TooLate ), null );
}
enlistmentList.Add( enlistment );
}
}
internal override void AddDependentClone()
{
lock ( this )
{
if ( -1 != phase )
{
throw TransactionException.CreateTransactionStateException( SR.GetString( SR.TraceSourceOletx ), null );
}
// We simply need to block the response to the proxy until all clone is completed.
this.incompleteDependentClones++;
}
}
internal override void DependentCloneCompleted()
{
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase1VolatileEnlistmentContainer.DependentCloneCompleted, outstandingNotifications = " +
this.outstandingNotifications.ToString( CultureInfo.CurrentCulture ) +
", incompleteDependentClones = " +
this.incompleteDependentClones.ToString( CultureInfo.CurrentCulture ) +
", phase = " + this.phase.ToString( CultureInfo.CurrentCulture );
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
//Fix for stress bug CSDMain 126887. This is to synchronize with the corresponding AddDependentClone
//which takes the container lock while incrementing the incompleteDependentClone count
lock (this)
{
this.incompleteDependentClones--;
}
Debug.Assert( 0 <= this.outstandingNotifications, "OletxPhase1VolatileEnlistmentContainer.DependentCloneCompleted - DependentCloneCompleted < 0" );
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase1VolatileEnlistmentContainer.DependentCloneCompleted";
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
}
internal override void RollbackFromTransaction()
{
bool voteNo = false;
IVoterBallotShim localVoterShim = null;
lock ( this )
{
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase1VolatileEnlistmentContainer.RollbackFromTransaction, outstandingNotifications = " +
this.outstandingNotifications.ToString( CultureInfo.CurrentCulture ) +
", incompleteDependentClones = " + this.incompleteDependentClones.ToString( CultureInfo.CurrentCulture );
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
if ( ( 1 == phase ) && ( 0 < this.outstandingNotifications ) )
{
this.alreadyVoted = true;
voteNo = true;
localVoterShim = this.voterBallotShim;
}
}
if ( voteNo )
{
try
{
if ( null != localVoterShim )
{
localVoterShim.Vote( false );
}
// We are not going to hear anymore from the proxy if we voted no, so we need to tell the
// enlistments to rollback. The state of the OletxVolatileEnlistment will determine whether or
// not the notification actually goes out to the app.
Aborted();
}
catch ( COMException ex )
{
if ( ( NativeMethods.XACT_E_CONNECTION_DOWN == ex.ErrorCode ) ||
( NativeMethods.XACT_E_TMNOTAVAILABLE == ex.ErrorCode )
)
{
lock ( this )
{
// If we are in phase 1, we need to tell the enlistments that the transaction is InDoubt.
if ( 1 == phase )
{
InDoubt();
}
}
if ( DiagnosticTrace.Verbose )
{
ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
ex );
}
}
else
{
throw;
}
}
finally
{
// At this point it is unclear if we will get a notification from DTC or not
// it depends on whether or not the transaction was in the process of aborting
// already. The only safe thing to do is to ensure that the Handle for the
// voter is released at this point.
HandleTable.FreeHandle(this.voterHandle);
}
}
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase1VolatileEnlistmentContainer.RollbackFromTransaction";
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
}
internal IVoterBallotShim VoterBallotShim
{
get
{
IVoterBallotShim returnValue = null;
lock ( this )
{
returnValue = this.voterBallotShim;
}
return returnValue;
}
set
{
lock ( this )
{
this.voterBallotShim = value;
}
}
}
internal override void DecrementOutstandingNotifications( bool voteYes )
{
bool respondToProxy = false;
IVoterBallotShim localVoterShim = null;
lock ( this )
{
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase1VolatileEnlistmentContainer.DecrementOutstandingNotifications, outstandingNotifications = " +
this.outstandingNotifications.ToString( CultureInfo.CurrentCulture ) +
", incompleteDependentClones = " +
this.incompleteDependentClones.ToString( CultureInfo.CurrentCulture );
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
this.outstandingNotifications--;
Debug.Assert( 0 <= this.outstandingNotifications, "OletxPhase1VolatileEnlistmentContainer.DecrementOutstandingNotifications - outstandingNotifications < 0" );
collectedVoteYes = collectedVoteYes && voteYes;
if ( 0 == outstandingNotifications )
{
if ( ( 1 == phase ) && ( ! this.alreadyVoted ) )
{
respondToProxy = true;
this.alreadyVoted = true;
localVoterShim = this.VoterBallotShim;
}
this.realOletxTransaction.DecrementUndecidedEnlistments();
}
}
try
{
if ( respondToProxy )
{
if ( ( collectedVoteYes ) && ( ! realOletxTransaction.Doomed ) )
{
if ( null != localVoterShim )
{
localVoterShim.Vote( true );
}
}
else // we need to vote no.
{
try
{
if ( null != localVoterShim )
{
localVoterShim.Vote( false );
}
// We are not going to hear anymore from the proxy if we voted no, so we need to tell the
// enlistments to rollback. The state of the OletxVolatileEnlistment will determine whether or
// not the notification actually goes out to the app.
Aborted();
}
finally
{
// At this point it is unclear if we will get a notification from DTC or not
// it depends on whether or not the transaction was in the process of aborting
// already. The only safe thing to do is to ensure that the Handle for the
// voter is released at this point.
HandleTable.FreeHandle(this.voterHandle);
}
}
}
}
catch ( COMException ex )
{
if ( ( NativeMethods.XACT_E_CONNECTION_DOWN == ex.ErrorCode ) ||
( NativeMethods.XACT_E_TMNOTAVAILABLE == ex.ErrorCode )
)
{
lock ( this )
{
// If we are in phase 1, we need to tell the enlistments that the transaction is InDoubt.
if ( 1 == phase )
{
InDoubt();
}
// There is nothing special to do for phase 2.
}
if ( DiagnosticTrace.Verbose )
{
ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
ex );
}
}
else
{
throw;
}
}
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase1VolatileEnlistmentContainer.DecrementOutstandingNotifications";
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
}
internal override void OutcomeFromTransaction( TransactionStatus outcome )
{
bool driveAbort = false;
bool driveInDoubt = false;
lock ( this )
{
// If we are in Phase 1 and still have outstanding notifications, we need
// to drive sending of the outcome to the enlistments. If we are in any
// other phase, or we don't have outstanding notifications, we will eventually
// get the outcome notification on our OWN voter enlistment, so we will just
// wait for that.
if ( ( 1 == this.phase ) && ( 0 < this.outstandingNotifications ) )
{
if ( TransactionStatus.Aborted == outcome )
{
driveAbort = true;
}
else if ( TransactionStatus.InDoubt == outcome )
{
driveInDoubt = true;
}
else
{
Debug.Assert( false, "OletxPhase1VolatileEnlistmentContainer.OutcomeFromTransaction, outcome is not Aborted or InDoubt" );
}
}
}
if ( driveAbort )
{
Aborted();
}
if ( driveInDoubt )
{
InDoubt();
}
}
internal override void Committed()
{
OletxVolatileEnlistment enlistment = null;
int localPhase1Count = 0;
lock ( this )
{
phase = 2;
localPhase1Count = this.enlistmentList.Count;
}
for ( int i = 0; i < localPhase1Count; i++ )
{
enlistment = this.enlistmentList[i] as OletxVolatileEnlistment;
if ( null == enlistment )
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxPhase1VolatileEnlistmentContainer.Committed, enlistmentList element is not an OletxVolatileEnlistment." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
enlistment.Commit();
}
}
internal override void Aborted()
{
OletxVolatileEnlistment enlistment = null;
int localPhase1Count = 0;
lock ( this )
{
phase = 2;
localPhase1Count = this.enlistmentList.Count;
}
for ( int i = 0; i < localPhase1Count; i++ )
{
enlistment = this.enlistmentList[i] as OletxVolatileEnlistment;
if ( null == enlistment )
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxPhase1VolatileEnlistmentContainer.Aborted, enlistmentList element is not an OletxVolatileEnlistment." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
enlistment.Rollback();
}
}
internal override void InDoubt()
{
OletxVolatileEnlistment enlistment = null;
int localPhase1Count = 0;
lock ( this )
{
phase = 2;
localPhase1Count = this.enlistmentList.Count;
}
for ( int i = 0; i < localPhase1Count; i++ )
{
enlistment = this.enlistmentList[i] as OletxVolatileEnlistment;
if ( null == enlistment )
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxPhase1VolatileEnlistmentContainer.InDoubt, enlistmentList element is not an OletxVolatileEnlistment." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
enlistment.InDoubt();
}
}
internal void VoteRequest()
{
OletxVolatileEnlistment enlistment = null;
int localPhase1Count = 0;
bool voteNo = false;
lock ( this )
{
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase1VolatileEnlistmentContainer.VoteRequest";
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
phase = 1;
// If we still have incomplete dependent clones, vote no now.
if ( 0 < this.incompleteDependentClones )
{
voteNo = true;
this.outstandingNotifications = 1;
}
else
{
this.outstandingNotifications = this.enlistmentList.Count;
localPhase1Count = this.enlistmentList.Count;
// We may not have an volatile phase 1 enlistments, which means that this
// container was created only for non-delay commit dependent clones. If that
// is the case, fake out a notification and response.
if ( 0 == localPhase1Count )
{
this.outstandingNotifications = 1;
}
}
this.realOletxTransaction.TooLateForEnlistments = true;
}
if ( voteNo )
{
DecrementOutstandingNotifications( false );
}
else if ( 0 == localPhase1Count )
{
DecrementOutstandingNotifications( true );
}
else
{
for ( int i = 0; i < localPhase1Count; i++ )
{
enlistment = this.enlistmentList[i] as OletxVolatileEnlistment;
if ( null == enlistment )
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxPhase1VolatileEnlistmentContainer.VoteRequest, enlistmentList element is not an OletxVolatileEnlistment." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
enlistment.Prepare( this );
}
}
if ( DiagnosticTrace.Verbose )
{
string description = "OletxPhase1VolatileEnlistmentContainer.VoteRequest";
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
description
);
}
}
}
class OletxVolatileEnlistment :
OletxBaseEnlistment,
IPromotedEnlistment
{
enum OletxVolatileEnlistmentState
{
Active,
Preparing,
Committing,
Aborting,
Prepared,
Aborted,
InDoubt,
Done
}
IEnlistmentNotificationInternal iEnlistmentNotification;
OletxVolatileEnlistmentState state = OletxVolatileEnlistmentState.Active;
OletxVolatileEnlistmentContainer container;
internal bool enlistDuringPrepareRequired;
// This is used if the transaction outcome is received while a prepare request
// is still outstanding to an app. Active means no outcome, yet. Aborted means
// we should tell the app Aborted. InDoubt means tell the app InDoubt. This
// should never be Committed because we shouldn't receive a Committed notification
// from the proxy while we have a Prepare outstanding.
TransactionStatus pendingOutcome;
internal OletxVolatileEnlistment(
IEnlistmentNotificationInternal enlistmentNotification,
EnlistmentOptions enlistmentOptions,
OletxTransaction oletxTransaction
) : base( null, oletxTransaction )
{
this.iEnlistmentNotification = enlistmentNotification;
this.enlistDuringPrepareRequired = (enlistmentOptions & EnlistmentOptions.EnlistDuringPrepareRequired) != 0;
// We get a container when we are asked to vote.
this.container = null;
pendingOutcome = TransactionStatus.Active;
if ( DiagnosticTrace.Information )
{
EnlistmentTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
this.InternalTraceIdentifier,
EnlistmentType.Volatile,
enlistmentOptions
);
}
}
internal void Prepare( OletxVolatileEnlistmentContainer container )
{
OletxVolatileEnlistmentState localState = OletxVolatileEnlistmentState.Active;
IEnlistmentNotificationInternal localEnlistmentNotification = null;
lock ( this )
{
localEnlistmentNotification = iEnlistmentNotification;
// The app may have already called EnlistmentDone. If this occurs, don't bother sending
// the notification to the app.
if ( OletxVolatileEnlistmentState.Active == state )
{
localState = state = OletxVolatileEnlistmentState.Preparing;
}
else
{
localState = state;
}
this.container = container;
}
// Tell the application to do the work.
if ( OletxVolatileEnlistmentState.Preparing == localState )
{
if ( null != localEnlistmentNotification )
{
if ( DiagnosticTrace.Verbose )
{
EnlistmentNotificationCallTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
this.InternalTraceIdentifier,
NotificationCall.Prepare
);
}
localEnlistmentNotification.Prepare( this );
}
else
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxVolatileEnlistment.Prepare, no enlistmentNotification member." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
}
else if ( OletxVolatileEnlistmentState.Done == localState )
{
// Voting yes because it was an early read-only vote.
container.DecrementOutstandingNotifications( true );
// We must have had a ---- between EnlistmentDone and the proxy telling
// us Phase0Request. Just return.
return;
}
// It is okay to be in Prepared state if we are edpr=true because we already
// did our prepare in Phase0.
else if ( ( OletxVolatileEnlistmentState.Prepared == localState ) &&
( this.enlistDuringPrepareRequired ) )
{
container.DecrementOutstandingNotifications( true );
return;
}
else if ( ( OletxVolatileEnlistmentState.Aborting == localState ) ||
( OletxVolatileEnlistmentState.Aborted == localState ) )
{
// An abort has raced with this volatile Prepare
// decrement the outstanding notifications making sure to vote no.
container.DecrementOutstandingNotifications( false );
return;
}
else
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxVolatileEnlistment.Prepare, invalid state." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
}
internal void Commit()
{
OletxVolatileEnlistmentState localState = OletxVolatileEnlistmentState.Active;
IEnlistmentNotificationInternal localEnlistmentNotification = null;
lock ( this )
{
// The app may have already called EnlistmentDone. If this occurs, don't bother sending
// the notification to the app and we don't need to tell the proxy.
if ( OletxVolatileEnlistmentState.Prepared == state )
{
localState = state = OletxVolatileEnlistmentState.Committing;
localEnlistmentNotification = iEnlistmentNotification;
}
else
{
localState = state;
}
}
// Tell the application to do the work.
if ( OletxVolatileEnlistmentState.Committing == localState )
{
if ( null != localEnlistmentNotification )
{
if ( DiagnosticTrace.Verbose )
{
EnlistmentNotificationCallTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
this.InternalTraceIdentifier,
NotificationCall.Commit
);
}
localEnlistmentNotification.Commit( this );
}
else
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxVolatileEnlistment.Commit, no enlistmentNotification member." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
}
else if ( OletxVolatileEnlistmentState.Done == localState )
{
// Early Exit - state was Done
}
else
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxVolatileEnlistment.Commit, invalid state." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
}
internal void Rollback()
{
OletxVolatileEnlistmentState localState = OletxVolatileEnlistmentState.Active;
IEnlistmentNotificationInternal localEnlistmentNotification = null;
lock ( this )
{
// The app may have already called EnlistmentDone. If this occurs, don't bother sending
// the notification to the app and we don't need to tell the proxy.
if ( ( OletxVolatileEnlistmentState.Prepared == state ) ||
( OletxVolatileEnlistmentState.Active == state )
)
{
localState = state = OletxVolatileEnlistmentState.Aborting;
localEnlistmentNotification = iEnlistmentNotification;
}
else
{
if ( OletxVolatileEnlistmentState.Preparing == state )
{
pendingOutcome = TransactionStatus.Aborted;
}
localState = state;
}
}
// Tell the application to do the work.
if ( OletxVolatileEnlistmentState.Aborting == localState )
{
if ( null != localEnlistmentNotification )
{
if ( DiagnosticTrace.Verbose )
{
EnlistmentNotificationCallTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
this.InternalTraceIdentifier,
NotificationCall.Rollback
);
}
localEnlistmentNotification.Rollback( this );
}
// There is a small ---- where Rollback could be called when the enlistment is already
// aborting the transaciton, so just ignore that call. When the app enlistment
// finishes responding to its Rollback notification with EnlistmentDone, things will get
// cleaned up.
}
else if ( OletxVolatileEnlistmentState.Preparing == localState )
{
// We need to tolerate this state, but we have already marked the
// enlistment as pendingRollback, so there is nothing else to do here.
}
else if ( OletxVolatileEnlistmentState.Done == localState )
{
// Early Exit - state was Done
}
else
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxVolatileEnlistment.Rollback, invalid state." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
}
internal void InDoubt()
{
OletxVolatileEnlistmentState localState = OletxVolatileEnlistmentState.Active;
IEnlistmentNotificationInternal localEnlistmentNotification = null;
lock ( this )
{
// The app may have already called EnlistmentDone. If this occurs, don't bother sending
// the notification to the app and we don't need to tell the proxy.
if ( OletxVolatileEnlistmentState.Prepared == state )
{
localState = state = OletxVolatileEnlistmentState.InDoubt;
localEnlistmentNotification = iEnlistmentNotification;
}
else
{
if ( OletxVolatileEnlistmentState.Preparing == state )
{
pendingOutcome = TransactionStatus.InDoubt;
}
localState = state;
}
}
// Tell the application to do the work.
if ( OletxVolatileEnlistmentState.InDoubt == localState )
{
if ( null != localEnlistmentNotification )
{
if ( DiagnosticTrace.Verbose )
{
EnlistmentNotificationCallTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
this.InternalTraceIdentifier,
NotificationCall.InDoubt
);
}
localEnlistmentNotification.InDoubt( this );
}
else
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxVolatileEnlistment.InDoubt, no enlistmentNotification member." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
}
else if ( OletxVolatileEnlistmentState.Preparing == localState )
{
// We have already set pendingOutcome, so there is nothing else to do.
}
else if ( OletxVolatileEnlistmentState.Done == localState )
{
// Early Exit - state was Done
}
else
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxVolatileEnlistment.InDoubt, invalid state." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
}
void IPromotedEnlistment.EnlistmentDone()
{
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxEnlistment.EnlistmentDone"
);
EnlistmentCallbackPositiveTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
this.InternalTraceIdentifier,
EnlistmentCallback.Done
);
}
OletxVolatileEnlistmentState localState = OletxVolatileEnlistmentState.Active;
OletxVolatileEnlistmentContainer localContainer = null;
lock ( this )
{
localState = state;
localContainer = container;
if ( ( OletxVolatileEnlistmentState.Active != state ) &&
( OletxVolatileEnlistmentState.Preparing != state ) &&
( OletxVolatileEnlistmentState.Aborting != state ) &&
( OletxVolatileEnlistmentState.Committing != state ) &&
( OletxVolatileEnlistmentState.InDoubt != state )
)
{
throw TransactionException.CreateEnlistmentStateException( SR.GetString( SR.TraceSourceOletx ), null, this.DistributedTxId );
}
state = OletxVolatileEnlistmentState.Done;
}
// For the Preparing state, we need to decrement the outstanding
// count with the container. If the state is Active, it is an early vote so we
// just stay in the Done state and when we get the Prepare, we will vote appropriately.
if ( OletxVolatileEnlistmentState.Preparing == localState )
{
if ( null != localContainer )
{
// Specify true. If aborting, it is okay because the transaction is already
// aborting.
localContainer.DecrementOutstandingNotifications( true );
}
}
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxEnlistment.EnlistmentDone"
);
}
}
void IPromotedEnlistment.Prepared()
{
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxPreparingEnlistment.Prepared"
);
EnlistmentCallbackPositiveTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
this.InternalTraceIdentifier,
EnlistmentCallback.Prepared
);
}
OletxVolatileEnlistmentContainer localContainer = null;
TransactionStatus localPendingOutcome = TransactionStatus.Active;
lock ( this )
{
if ( OletxVolatileEnlistmentState.Preparing != state )
{
throw TransactionException.CreateEnlistmentStateException( SR.GetString( SR.TraceSourceOletx ), null, this.DistributedTxId );
}
state = OletxVolatileEnlistmentState.Prepared;
localPendingOutcome = pendingOutcome;
if ( null == container )
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxVolatileEnlistment.Prepared, no container member." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
localContainer = container;
}
// Vote yes.
localContainer.DecrementOutstandingNotifications( true );
switch ( localPendingOutcome )
{
case TransactionStatus.Active:
{
// nothing to do. Everything is proceeding as normal.
break;
}
case TransactionStatus.Aborted:
{
// The transaction aborted while the Prepare was outstanding.
// We need to tell the app to rollback.
Rollback();
break;
}
case TransactionStatus.InDoubt:
{
// The transaction went InDoubt while the Prepare was outstanding.
// We need to tell the app.
InDoubt();
break;
}
default:
{
// This shouldn't happen.
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxVolatileEnlistment.Prepared, invalid pending outcome value." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
}
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxPreparingEnlistment.Prepared"
);
}
}
void IPromotedEnlistment.ForceRollback()
{
((IPromotedEnlistment)this).ForceRollback( null );
}
void IPromotedEnlistment.ForceRollback(Exception e)
{
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxPreparingEnlistment.ForceRollback"
);
}
if ( DiagnosticTrace.Warning )
{
EnlistmentCallbackNegativeTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
this.InternalTraceIdentifier,
EnlistmentCallback.ForceRollback
);
}
OletxVolatileEnlistmentContainer localContainer = null;
lock ( this )
{
if ( OletxVolatileEnlistmentState.Preparing != state )
{
throw TransactionException.CreateEnlistmentStateException( SR.GetString( SR.TraceSourceOletx ), null, this.DistributedTxId );
}
// There are no more notifications that need to happen on this enlistment.
state = OletxVolatileEnlistmentState.Done;
if ( null == container )
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
""
);
}
Debug.Assert( false, "OletxVolatileEnlistment.ForceRollback, no container member." );
throw new InvalidOperationException( SR.GetString( SR.InternalError ) );
}
localContainer = container;
}
Interlocked.CompareExchange<Exception>( ref this.oletxTransaction.realOletxTransaction.innerException, e, null );
// Vote no.
localContainer.DecrementOutstandingNotifications( false );
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxPreparingEnlistment.ForceRollback"
);
}
}
void IPromotedEnlistment.Committed()
{
throw new InvalidOperationException();
}
void IPromotedEnlistment.Aborted()
{
throw new InvalidOperationException();
}
void IPromotedEnlistment.Aborted(Exception e)
{
throw new InvalidOperationException();
}
void IPromotedEnlistment.InDoubt()
{
throw new InvalidOperationException();
}
void IPromotedEnlistment.InDoubt(Exception e)
{
throw new InvalidOperationException();
}
byte[] IPromotedEnlistment.GetRecoveryInformation()
{
throw TransactionException.CreateInvalidOperationException( SR.GetString( SR.TraceSourceOletx ),
SR.GetString( SR.VolEnlistNoRecoveryInfo), null, this.DistributedTxId );
}
InternalEnlistment IPromotedEnlistment.InternalEnlistment
{
get
{
return this.internalEnlistment;
}
set
{
this.internalEnlistment = value;
}
}
}
}
|