|
//-----------------------------------------------------------------------------
// <copyright file="OletxTransaction.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Configuration;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.Threading;
using System.Transactions.Diagnostics;
namespace System.Transactions.Oletx
{
/// <summary>
/// A Transaction object represents a single transaction. It is created by TransactionManager
/// objects through CreateTransaction or through deserialization. Alternatively, the static Create
/// methodis provided, which creates a "default" TransactionManager and requests that it create
/// a new transaction with default values. A transaction can only be committed by
/// the client application that created the transaction. If a client application wishes to allow
/// access to the transaction by multiple threads, but wants to prevent those other threads from
/// committing the transaction, the application can make a "clone" of the transaction. Transaction
/// clones have the same capabilities as the original transaction, except for the ability to commit
/// the transaction.
/// </summary>
[Serializable]
internal class OletxTransaction : ISerializable, IObjectReference
{
// We have a strong reference on realOletxTransaction which does the real work
internal RealOletxTransaction realOletxTransaction = null;
// String that is used as a name for the propagationToken
// while serializing and deserializing this object
protected const string propagationTokenString = "OletxTransactionPropagationToken";
// When an OletxTransaction is being created via deserialization, this member is
// filled with the propagation token from the serialization info. Later, when
// GetRealObject is called, this array is used to decide whether or not a new
// transation needs to be created and if so, to create the transaction.
private byte[] propagationTokenForDeserialize = null;
protected int disposed = 0;
// In GetRealObject, we ask LTM if it has a promoted transaction with the same ID. If it does,
// we need to remember that transaction because GetRealObject is called twice during
// deserialization. In this case, GetRealObject returns the LTM transaction, not this OletxTransaction.
// The OletxTransaction will get GC'd because there will be no references to it.
internal Transaction savedLtmPromotedTransaction = null;
private TransactionTraceIdentifier traceIdentifier = TransactionTraceIdentifier.Empty;
// Property
internal RealOletxTransaction RealTransaction
{
get
{
return this.realOletxTransaction;
}
}
internal Guid Identifier
{
get
{
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.get_Identifier"
);
}
Guid returnValue = this.realOletxTransaction.Identifier;
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.get_Identifier"
);
}
return returnValue;
}
}
internal Guid DistributedTxId
{
get
{
Guid returnValue = Guid.Empty;
if (this.realOletxTransaction != null && this.realOletxTransaction.InternalTransaction != null)
{
returnValue = this.realOletxTransaction.InternalTransaction.DistributedTxId;
}
return returnValue;
}
}
internal System.Transactions.TransactionStatus Status
{
get
{
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.get_Status"
);
}
TransactionStatus returnValue = this.realOletxTransaction.Status;
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.get_Status"
);
}
return returnValue;
}
}
internal Exception InnerException
{
get
{
return this.realOletxTransaction.innerException;
}
}
internal OletxTransaction(RealOletxTransaction realOletxTransaction)
{
this.realOletxTransaction = realOletxTransaction;
// Tell the realOletxTransaction that we are here.
this.realOletxTransaction.OletxTransactionCreated();
}
protected OletxTransaction(SerializationInfo serializationInfo, StreamingContext context)
{
if (serializationInfo == null)
{
throw new ArgumentNullException( "serializationInfo");
}
// Simply store the propagation token from the serialization info. GetRealObject will
// decide whether or not we will use it.
propagationTokenForDeserialize = (byte[])serializationInfo.GetValue(propagationTokenString, typeof(byte[]));
if ( propagationTokenForDeserialize.Length < 24 )
{
throw new ArgumentException( SR.GetString( SR.InvalidArgument ), "serializationInfo" );
}
}
public object GetRealObject(
StreamingContext context
)
{
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"IObjectReference.GetRealObject"
);
}
if ( null == propagationTokenForDeserialize )
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
SR.GetString( SR.UnableToDeserializeTransaction )
);
}
throw TransactionException.Create( SR.GetString( SR.TraceSourceOletx ), SR.GetString( SR.UnableToDeserializeTransactionInternalError ), null );
}
// This may be a second call. If so, just return.
if ( null != this.savedLtmPromotedTransaction )
{
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"IObjectReference.GetRealObject"
);
}
return this.savedLtmPromotedTransaction;
}
Transaction returnValue = TransactionInterop.GetTransactionFromTransmitterPropagationToken( propagationTokenForDeserialize );
Debug.Assert( null != returnValue, "OletxTransaction.GetRealObject - GetTxFromPropToken returned null" );
this.savedLtmPromotedTransaction = returnValue;
if ( DiagnosticTrace.Verbose )
{
TransactionDeserializedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
returnValue.internalTransaction.PromotedTransaction.TransactionTraceId
);
}
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"IObjectReference.GetRealObject"
);
}
return returnValue;
}
/// <summary>
/// Implementation of IDisposable.Dispose. Releases managed, and unmanaged resources
/// associated with the Transaction object.
/// </summary>
internal void Dispose()
{
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"IDisposable.Dispose"
);
}
int localDisposed = Interlocked.CompareExchange( ref this.disposed, 1, 0 );
if ( 0 == localDisposed )
{
this.realOletxTransaction.OletxTransactionDisposed();
}
GC.SuppressFinalize (this);
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"IDisposable.Dispose"
);
}
}
// Specific System.Transactions implementation
/// <summary>
/// Initiates commit processing of the transaction. The caller must have created the transaction
/// as a new transaction through TransactionManager.CreateTransaction or Transaction.Create.
///
/// If the transaction is already aborted due to some other participant making a Rollback call,
/// the transaction timeout period expiring, or some sort of network failure, an exception will
/// be raised.
/// </summary>
/// <summary>
/// Initiates rollback processing of the transaction. This method can be called on any instance
/// of a Transaction class, regardless of how the Transaction was obtained. It is possible for this
/// method to be called "too late", after the outcome of the transaction has already been determined.
/// In this case, an exception is raised.
internal void Rollback()
{
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.Rollback"
);
}
if ( DiagnosticTrace.Warning )
{
TransactionRollbackCalledTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
this.TransactionTraceId
);
}
Debug.Assert( ( 0 == this.disposed ), "OletxTransction object is disposed" );
this.realOletxTransaction.Rollback();
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.Rollback"
);
}
}
internal IPromotedEnlistment EnlistVolatile(
ISinglePhaseNotificationInternal singlePhaseNotification,
EnlistmentOptions enlistmentOptions
)
{
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.EnlistVolatile( ISinglePhaseNotificationInternal )"
);
}
Debug.Assert( null != singlePhaseNotification, "Argument is null" );
Debug.Assert( ( 0 == this.disposed ), "OletxTransction object is disposed" );
if ( this.realOletxTransaction == null || this.realOletxTransaction.TooLateForEnlistments )
{
throw TransactionException.Create(SR.GetString(SR.TraceSourceOletx),
SR.GetString(SR.TooLate), null, this.DistributedTxId);
}
IPromotedEnlistment enlistment = realOletxTransaction.EnlistVolatile(
singlePhaseNotification,
enlistmentOptions,
this
);
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.EnlistVolatile( ISinglePhaseNotificationInternal )"
);
}
return enlistment;
}
internal IPromotedEnlistment EnlistVolatile(
IEnlistmentNotificationInternal enlistmentNotification,
EnlistmentOptions enlistmentOptions
)
{
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.EnlistVolatile( IEnlistmentNotificationInternal )"
);
}
Debug.Assert( null != enlistmentNotification, "Argument is null" );
Debug.Assert( ( 0 == this.disposed ), "OletxTransction object is disposed" );
if ( this.realOletxTransaction == null || this.realOletxTransaction.TooLateForEnlistments )
{
throw TransactionException.Create(SR.GetString(SR.TraceSourceOletx),
SR.GetString(SR.TooLate), null, this.DistributedTxId);
}
IPromotedEnlistment enlistment = realOletxTransaction.EnlistVolatile(
enlistmentNotification,
enlistmentOptions,
this
);
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.EnlistVolatile( IEnlistmentNotificationInternal )"
);
}
return enlistment;
}
internal IPromotedEnlistment EnlistDurable(
Guid resourceManagerIdentifier,
ISinglePhaseNotificationInternal singlePhaseNotification,
bool canDoSinglePhase,
EnlistmentOptions enlistmentOptions
)
{
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.EnlistDurable( ISinglePhaseNotificationInternal )"
);
}
Debug.Assert( ( 0 == this.disposed ), "OletxTransction object is disposed" );
if ( this.realOletxTransaction == null || this.realOletxTransaction.TooLateForEnlistments )
{
throw TransactionException.Create( SR.GetString( SR.TraceSourceOletx ),
SR.GetString( SR.TooLate ), null, this.DistributedTxId);
}
// get the Oletx TM from the real class
OletxTransactionManager oletxTM = realOletxTransaction.OletxTransactionManagerInstance;
// get the resource manager from the Oletx TM
OletxResourceManager rm = oletxTM.FindOrRegisterResourceManager(resourceManagerIdentifier);
// ask the rm to do the durable enlistment
OletxEnlistment enlistment = rm.EnlistDurable(
this,
canDoSinglePhase,
singlePhaseNotification,
enlistmentOptions
);
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.EnlistDurable( ISinglePhaseNotificationInternal )"
);
}
return enlistment;
}
internal OletxDependentTransaction DependentClone(
bool delayCommit
)
{
OletxDependentTransaction dependentClone = null;
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.DependentClone"
);
}
Debug.Assert( ( 0 == this.disposed ), "OletxTransction object is disposed" );
if (TransactionStatus.Aborted == Status)
{
throw TransactionAbortedException.Create(SR.GetString(SR.TraceSourceOletx), SR.GetString(SR.TransactionAborted), realOletxTransaction.innerException, this.DistributedTxId);
}
if (TransactionStatus.InDoubt == Status)
{
throw TransactionInDoubtException.Create(SR.GetString(SR.TraceSourceOletx), SR.GetString(SR.TransactionIndoubt), realOletxTransaction.innerException, this.DistributedTxId);
}
if (TransactionStatus.Active != Status)
{
throw TransactionException.Create(SR.GetString(SR.TraceSourceOletx), SR.GetString(SR.TransactionAlreadyOver), null, this.DistributedTxId);
}
dependentClone = new OletxDependentTransaction( realOletxTransaction, delayCommit );
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.DependentClone"
);
}
return dependentClone;
}
internal TransactionTraceIdentifier TransactionTraceId
{
get
{
if ( TransactionTraceIdentifier.Empty == this.traceIdentifier )
{
lock ( this.realOletxTransaction )
{
if ( TransactionTraceIdentifier.Empty == this.traceIdentifier )
{
try
{
TransactionTraceIdentifier temp = new TransactionTraceIdentifier( this.realOletxTransaction.Identifier.ToString(), 0 );
Thread.MemoryBarrier();
this.traceIdentifier = temp;
}
catch ( TransactionException ex )
{
// realOletxTransaction.Identifier throws a TransactionException if it can't determine the guid of the
// transaction because the transaction was already committed or aborted before the RealOletxTransaction was
// created. If that happens, we don't want to throw just because we are trying to trace. So just use
// the TransactionTraceIdentifier.Empty.
if ( DiagnosticTrace.Verbose )
{
ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
ex );
}
}
}
}
}
return this.traceIdentifier;
}
}
[SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
public void GetObjectData(SerializationInfo serializationInfo, StreamingContext context)
{
if (serializationInfo == null)
{
throw new ArgumentNullException( "serializationInfo");
}
byte[] propagationToken = null;
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.GetObjectData"
);
}
Debug.Assert( ( 0 == this.disposed ), "OletxTransction object is disposed" );
propagationToken = TransactionInterop.GetTransmitterPropagationToken( this );
serializationInfo.SetType( typeof( OletxTransaction ) );
serializationInfo.AddValue(propagationTokenString, propagationToken);
if ( DiagnosticTrace.Information )
{
TransactionSerializedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
this.TransactionTraceId
);
}
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
"OletxTransaction.GetObjectData"
);
}
}
virtual public System.Transactions.IsolationLevel IsolationLevel
{
get { return ( this.realOletxTransaction.TransactionIsolationLevel ); }
}
}
// Internal class used by OletxTransaction class which is public
internal class RealOletxTransaction
{
// Transaction manager
private OletxTransactionManager oletxTransactionManager;
private ITransactionShim transactionShim;
// guid related to transaction
private System.Guid txGuid;
// Isolation level of the transaction
private IsolationLevel isolationLevel;
// Record the exception that caused the transaction to abort.
internal Exception innerException;
// Store status
private TransactionStatus status;
// This is the count of undisposed OletxTransaction objects that reference
// this RealOletxTransaction. This is incremented when an OletxTransaction is created
// and decremented when OletxTransactionDisposed is
// called. When it is decremented to zero, the transactionShim
// field is "released", thus releasing the unmanged proxy interface
// pointer.
private int undisposedOletxTransactionCount;
// The list of containers for phase0 volatile enlistment multiplexing so we only enlist with the proxy once per wave.
// The last one on the list is the "current" one.
internal ArrayList phase0EnlistVolatilementContainerList;
// The container for phase1 volatile enlistment multiplexing so we only enlist with the proxy once.
internal OletxPhase1VolatileEnlistmentContainer phase1EnlistVolatilementContainer;
// Used to get outcomes of transactions with a voter.
private OutcomeEnlistment outcomeEnlistment = null;
// This is a count of volatile and Phase0 durable enlistments on this transaction that have not yet voted.
// This is incremented when an enlistment is made and decremented when the
// enlistment votes. It is checked in Rollback. If the count is greater than 0,
// then the doomed field is set to true and the Rollback is allowed. If the count
// is zero in Rollback, the rollback is rejected with a "too late" exception.
// All checking and modification of this field needs to be done under a lock( this ).
private int undecidedEnlistmentCount = 0;
// If true, indicates that the transaction should NOT commit. This is set to
// true if Rollback is called when there are outstanding enlistments. This is
// checked when enlistments vote Prepared. If true, then the enlistment's vote
// is turned into a ForceRollback. All checking and modification of this field
// needs to be done under a lock (this).
private bool doomed = false;
// This property is used to allocate enlistment identifiers for enlistment trace identifiers.
// It is only incremented when a new enlistment is created for this instance of RealOletxTransaction.
// Enlistments on all clones of this Real transaction use this value.
internal int enlistmentCount = 0;
private DateTime creationTime;
private DateTime lastStateChangeTime;
private TransactionTraceIdentifier traceIdentifier = TransactionTraceIdentifier.Empty;
// This field is set directly from the OletxCommittableTransaction constructor. It will be null
// for non-root RealOletxTransactions.
internal OletxCommittableTransaction committableTransaction;
// This is an internal OletxTransaction. It is created as part of the RealOletxTransaction constructor.
// It is used by the DependentCloneEnlistments when creating their volatile enlistments.
internal OletxTransaction internalClone;
// This is set initialized to false. It is set to true when the OletxPhase1VolatileContainer gets a VoteRequest or
// when any OletxEnlistment attached to this transaction gets a PrepareRequest. At that point, it is too late for any
// more enlistments.
private bool tooLateForEnlistments;
// This is the InternalTransaction that instigated creation of this RealOletxTransaction. When we get the outcome
// of the transaction, we use this to notify the InternalTransaction of the outcome. We do this to avoid the LTM
// always creating a volatile enlistment just to get the outcome.
private InternalTransaction internalTransaction;
internal InternalTransaction InternalTransaction
{
get
{
return this.internalTransaction;
}
set
{
this.internalTransaction = value;
}
}
internal OletxTransactionManager OletxTransactionManagerInstance
{
get
{
return oletxTransactionManager;
}
}
internal Guid Identifier
{
get
{
// The txGuid will be empty if the oletx transaction was already committed or aborted when we
// tried to create the RealOletxTransaction. We still allow creation of the RealOletxTransaction
// for COM+ interop purposes, but we can't get the guid or the status of the transaction.
if ( txGuid.Equals( Guid.Empty ) )
{
throw TransactionException.Create( SR.GetString( SR.TraceSourceOletx ), SR.GetString ( SR.CannotGetTransactionIdentifier ), null );
}
return this.txGuid;
}
}
internal Guid DistributedTxId
{
get
{
Guid returnValue = Guid.Empty;
if (this.InternalTransaction != null)
{
returnValue = this.InternalTransaction.DistributedTxId;
}
return returnValue;
}
}
internal IsolationLevel TransactionIsolationLevel
{
get
{
return this.isolationLevel;
}
}
internal TransactionStatus Status
{
get
{
return this.status;
}
}
internal System.Guid TxGuid
{
get
{
return this.txGuid;
}
}
internal void IncrementUndecidedEnlistments()
{
// Avoid taking a lock on the transaction here. Decrement
// will be called by a thread owning a lock on enlistment
// containers. When creating new enlistments the transaction
// will attempt to get a lock on the container when it
// already holds a lock on the transaction. This can result
// in a deadlock.
Interlocked.Increment(ref this.undecidedEnlistmentCount);
}
internal void DecrementUndecidedEnlistments()
{
// Avoid taking a lock on the transaction here. Decrement
// will be called by a thread owning a lock on enlistment
// containers. When creating new enlistments the transaction
// will attempt to get a lock on the container when it
// already holds a lock on the transaction. This can result
// in a deadlock.
Interlocked.Decrement(ref this.undecidedEnlistmentCount);
}
internal int UndecidedEnlistments
{
get
{
return this.undecidedEnlistmentCount;
}
}
internal bool Doomed
{
get
{
return this.doomed;
}
}
internal ITransactionShim TransactionShim
{
get
{
ITransactionShim shim = this.transactionShim;
if ( null == shim )
{
throw TransactionInDoubtException.Create( SR.GetString( SR.TraceSourceOletx ), SR.GetString(SR.TransactionIndoubt), null, this.DistributedTxId );
}
return shim;
}
}
// Common constructor used by all types of constructors
// Create a clean and fresh transaction.
internal RealOletxTransaction(OletxTransactionManager transactionManager,
ITransactionShim transactionShim,
OutcomeEnlistment outcomeEnlistment,
Guid identifier,
OletxTransactionIsolationLevel oletxIsoLevel,
bool isRoot )
{
bool successful = false;
try
{
// initialize the member fields
this.oletxTransactionManager = transactionManager;
this.transactionShim = transactionShim;
this.outcomeEnlistment = outcomeEnlistment;
this.txGuid = identifier;
this.isolationLevel = OletxTransactionManager.ConvertIsolationLevelFromProxyValue( oletxIsoLevel );
this.status = TransactionStatus.Active;
this.undisposedOletxTransactionCount = 0;
this.phase0EnlistVolatilementContainerList = null;
this.phase1EnlistVolatilementContainer = null;
this.tooLateForEnlistments = false;
this.internalTransaction = null;
this.creationTime = DateTime.UtcNow;
this.lastStateChangeTime = this.creationTime;
// Connect this object with the OutcomeEnlistment.
this.internalClone = new OletxTransaction( this );
// We have have been created without an outcome enlistment if it was too late to create
// a clone from the ITransactionNative that we were created from.
if ( null != this.outcomeEnlistment )
{
this.outcomeEnlistment.SetRealTransaction( this );
}
else
{
this.status = TransactionStatus.InDoubt;
}
if ( DiagnosticTrace.HaveListeners )
{
DiagnosticTrace.TraceTransfer(this.txGuid);
}
successful = true;
}
finally
{
if (!successful)
{
if (this.outcomeEnlistment != null)
{
this.outcomeEnlistment.UnregisterOutcomeCallback();
this.outcomeEnlistment = null;
}
}
}
}
internal bool TooLateForEnlistments
{
get
{
return this.tooLateForEnlistments;
}
set
{
this.tooLateForEnlistments = value;
}
}
internal OletxVolatileEnlistmentContainer AddDependentClone( bool delayCommit )
{
IPhase0EnlistmentShim phase0Shim = null;
IVoterBallotShim voterShim = null;
bool needVoterEnlistment = false;
bool needPhase0Enlistment = false;
OletxVolatileEnlistmentContainer returnValue = null;
OletxPhase0VolatileEnlistmentContainer localPhase0VolatileContainer = null;
OletxPhase1VolatileEnlistmentContainer localPhase1VolatileContainer = null;
bool enlistmentSucceeded = false;
bool phase0ContainerLockAcquired = false;
IntPtr phase0Handle = IntPtr.Zero;
// Yes, we are talking to the proxy while holding the lock on the RealOletxTransaction.
// If we don't then things get real sticky with other threads allocating containers.
// We only do this the first time we get a depenent clone of a given type (delay vs. non-delay).
// After that, we don't create a new container, except for Phase0 if we need to create one
// for a second wave.
RuntimeHelpers.PrepareConstrainedRegions();
try
{
lock ( this )
{
if ( delayCommit )
{
if ( null == this.phase0EnlistVolatilementContainerList )
{
// Not using a MemoryBarrier because all access to this member variable is under a lock of the
// object.
this.phase0EnlistVolatilementContainerList = new ArrayList(1);
}
// We may have failed the proxy enlistment for the first container, but we would have
// allocated the list. That is why we have this check here.
if ( 0 == this.phase0EnlistVolatilementContainerList.Count )
{
localPhase0VolatileContainer = new OletxPhase0VolatileEnlistmentContainer( this );
needPhase0Enlistment = true;
}
else
{
localPhase0VolatileContainer = this.phase0EnlistVolatilementContainerList[this.phase0EnlistVolatilementContainerList.Count - 1] as OletxPhase0VolatileEnlistmentContainer;
if (localPhase0VolatileContainer != null)
{
//CSDMain 91509 - We now synchronize this call with the shim notification trying to call Phase0Request on this container
TakeContainerLock(localPhase0VolatileContainer, ref phase0ContainerLockAcquired);
}
if ( ! localPhase0VolatileContainer.NewEnlistmentsAllowed )
{
//It is OK to release the lock at this time because we are creating a new container that has not yet
//been enlisted with DTC. So there is no ---- to worry about
ReleaseContainerLock(localPhase0VolatileContainer, ref phase0ContainerLockAcquired);
localPhase0VolatileContainer = new OletxPhase0VolatileEnlistmentContainer( this );
needPhase0Enlistment = true;
}
else
{
needPhase0Enlistment = false;
}
}
if ( needPhase0Enlistment )
{
// We need to create a VoterNotifyShim if native threads are not allowed to enter managed code.
phase0Handle = HandleTable.AllocHandle( localPhase0VolatileContainer );
}
}
else // ! delayCommit
{
if ( null == this.phase1EnlistVolatilementContainer )
{
localPhase1VolatileContainer = new OletxPhase1VolatileEnlistmentContainer( this );
needVoterEnlistment = true;
// We need to create a VoterNotifyShim.
localPhase1VolatileContainer.voterHandle =
HandleTable.AllocHandle( localPhase1VolatileContainer );
}
else
{
needVoterEnlistment = false;
localPhase1VolatileContainer = this.phase1EnlistVolatilementContainer;
}
}
try
{
//At this point, we definitely need the lock on the phase0 container so that it doesnt ---- with shim notifications from unmanaged code
//corrupting state while we are in the middle of an AddDependentClone processing
if (localPhase0VolatileContainer != null)
{
TakeContainerLock(localPhase0VolatileContainer, ref phase0ContainerLockAcquired);
}
// If enlistDuringPrepareRequired is true, we need to ask the proxy to create a Phase0 enlistment.
if ( needPhase0Enlistment )
{
// We need to use shims if native threads are not allowed to enter managed code.
this.transactionShim.Phase0Enlist(
phase0Handle,
out phase0Shim );
localPhase0VolatileContainer.Phase0EnlistmentShim = phase0Shim;
}
if ( needVoterEnlistment )
{
// We need to use shims if native threads are not allowed to enter managed code.
OletxTransactionManagerInstance.dtcTransactionManagerLock.AcquireReaderLock( -1 );
try
{
this.transactionShim.CreateVoter(
localPhase1VolatileContainer.voterHandle,
out voterShim );
enlistmentSucceeded = true;
}
finally
{
OletxTransactionManagerInstance.dtcTransactionManagerLock.ReleaseReaderLock();
}
localPhase1VolatileContainer.VoterBallotShim = voterShim;
}
if ( delayCommit )
{
// if we needed a Phase0 enlistment, we need to add the container to the
// list.
if ( needPhase0Enlistment )
{
this.phase0EnlistVolatilementContainerList.Add( localPhase0VolatileContainer );
}
localPhase0VolatileContainer.AddDependentClone();
returnValue = localPhase0VolatileContainer;
}
else
{
// If we needed a voter enlistment, we need to save the container as THE
// phase1 container for this transaction.
if ( needVoterEnlistment )
{
Debug.Assert( ( null == this.phase1EnlistVolatilementContainer ),
"RealOletxTransaction.AddDependentClone - phase1VolContainer not null when expected" );
this.phase1EnlistVolatilementContainer = localPhase1VolatileContainer;
}
localPhase1VolatileContainer.AddDependentClone();
returnValue = localPhase1VolatileContainer;
}
}
catch (COMException comException)
{
OletxTransactionManager.ProxyException( comException );
throw;
}
}
}
finally
{
//First release the lock on the phase 0 container if it was acquired. Any work on localPhase0VolatileContainer
//that needs its state to be consistent while processing should do so before this statement is executed.
if (localPhase0VolatileContainer != null)
{
ReleaseContainerLock(localPhase0VolatileContainer, ref phase0ContainerLockAcquired);
}
if ( phase0Handle != IntPtr.Zero && localPhase0VolatileContainer.Phase0EnlistmentShim == null )
{
HandleTable.FreeHandle( phase0Handle );
}
if ( !enlistmentSucceeded &&
null != localPhase1VolatileContainer &&
localPhase1VolatileContainer.voterHandle != IntPtr.Zero &&
needVoterEnlistment )
{
HandleTable.FreeHandle( localPhase1VolatileContainer.voterHandle );
}
}
return returnValue;
}
void ReleaseContainerLock(OletxPhase0VolatileEnlistmentContainer localPhase0VolatileContainer, ref bool phase0ContainerLockAcquired)
{
if (phase0ContainerLockAcquired)
{
Monitor.Exit(localPhase0VolatileContainer);
phase0ContainerLockAcquired = false;
}
}
void TakeContainerLock(OletxPhase0VolatileEnlistmentContainer localPhase0VolatileContainer, ref bool phase0ContainerLockAcquired)
{
if (!phase0ContainerLockAcquired)
{
#pragma warning disable 0618
//@
Monitor.Enter(localPhase0VolatileContainer);
#pragma warning restore 0618
phase0ContainerLockAcquired = true;
}
}
internal IPromotedEnlistment CommonEnlistVolatile(
IEnlistmentNotificationInternal enlistmentNotification,
EnlistmentOptions enlistmentOptions,
OletxTransaction oletxTransaction
)
{
OletxVolatileEnlistment enlistment = null;
bool needVoterEnlistment = false;
bool needPhase0Enlistment = false;
OletxPhase0VolatileEnlistmentContainer localPhase0VolatileContainer = null;
OletxPhase1VolatileEnlistmentContainer localPhase1VolatileContainer = null;
IntPtr phase0Handle = IntPtr.Zero;
IVoterBallotShim voterShim = null;
IPhase0EnlistmentShim phase0Shim = null;
bool enlistmentSucceeded = false;
// Yes, we are talking to the proxy while holding the lock on the RealOletxTransaction.
// If we don't then things get real sticky with other threads allocating containers.
// We only do this the first time we get a depenent clone of a given type (delay vs. non-delay).
// After that, we don't create a new container, except for Phase0 if we need to create one
// for a second wave.
RuntimeHelpers.PrepareConstrainedRegions();
try
{
lock ( this )
{
enlistment = new OletxVolatileEnlistment(
enlistmentNotification,
enlistmentOptions,
oletxTransaction
);
if ( (enlistmentOptions & EnlistmentOptions.EnlistDuringPrepareRequired) != 0 )
{
if ( null == this.phase0EnlistVolatilementContainerList )
{
// Not using a MemoryBarrier because all access to this member variable is done when holding
// a lock on the object.
this.phase0EnlistVolatilementContainerList = new ArrayList(1);
}
// We may have failed the proxy enlistment for the first container, but we would have
// allocated the list. That is why we have this check here.
if ( 0 == this.phase0EnlistVolatilementContainerList.Count )
{
localPhase0VolatileContainer = new OletxPhase0VolatileEnlistmentContainer( this );
needPhase0Enlistment = true;
}
else
{
localPhase0VolatileContainer = this.phase0EnlistVolatilementContainerList[this.phase0EnlistVolatilementContainerList.Count - 1] as OletxPhase0VolatileEnlistmentContainer;
if ( ! localPhase0VolatileContainer.NewEnlistmentsAllowed )
{
localPhase0VolatileContainer = new OletxPhase0VolatileEnlistmentContainer( this );
needPhase0Enlistment = true;
}
else
{
needPhase0Enlistment = false;
}
}
if ( needPhase0Enlistment )
{
// We need to create a VoterNotifyShim if native threads are not allowed to enter managed code.
phase0Handle = HandleTable.AllocHandle( localPhase0VolatileContainer );
}
}
else // not EDPR = TRUE - may need a voter...
{
if ( null == this.phase1EnlistVolatilementContainer )
{
needVoterEnlistment = true;
localPhase1VolatileContainer = new OletxPhase1VolatileEnlistmentContainer( this );
// We need to create a VoterNotifyShim.
localPhase1VolatileContainer.voterHandle =
HandleTable.AllocHandle( localPhase1VolatileContainer );
}
else
{
needVoterEnlistment = false;
localPhase1VolatileContainer = this.phase1EnlistVolatilementContainer;
}
}
try
{
// If enlistDuringPrepareRequired is true, we need to ask the proxy to create a Phase0 enlistment.
if ( needPhase0Enlistment )
{
lock ( localPhase0VolatileContainer )
{
transactionShim.Phase0Enlist(
phase0Handle,
out phase0Shim );
localPhase0VolatileContainer.Phase0EnlistmentShim = phase0Shim;
}
}
if ( needVoterEnlistment )
{
this.transactionShim.CreateVoter(
localPhase1VolatileContainer.voterHandle,
out voterShim );
enlistmentSucceeded = true;
localPhase1VolatileContainer.VoterBallotShim = voterShim;
}
if ( (enlistmentOptions & EnlistmentOptions.EnlistDuringPrepareRequired) != 0 )
{
localPhase0VolatileContainer.AddEnlistment(
enlistment
);
if ( needPhase0Enlistment )
{
this.phase0EnlistVolatilementContainerList.Add( localPhase0VolatileContainer );
}
}
else
{
localPhase1VolatileContainer.AddEnlistment(
enlistment
);
if ( needVoterEnlistment )
{
Debug.Assert( ( null == this.phase1EnlistVolatilementContainer ),
"RealOletxTransaction.CommonEnlistVolatile - phase1VolContainer not null when expected." );
this.phase1EnlistVolatilementContainer = localPhase1VolatileContainer;
}
}
}
catch (COMException comException)
{
OletxTransactionManager.ProxyException( comException );
throw;
}
}
}
finally
{
if ( phase0Handle != IntPtr.Zero && localPhase0VolatileContainer.Phase0EnlistmentShim == null )
{
HandleTable.FreeHandle( phase0Handle );
}
if ( !enlistmentSucceeded &&
null != localPhase1VolatileContainer &&
localPhase1VolatileContainer.voterHandle != IntPtr.Zero &&
needVoterEnlistment )
{
HandleTable.FreeHandle( localPhase1VolatileContainer.voterHandle );
}
}
return enlistment;
}
internal IPromotedEnlistment EnlistVolatile(
ISinglePhaseNotificationInternal enlistmentNotification,
EnlistmentOptions enlistmentOptions,
OletxTransaction oletxTransaction
)
{
IPromotedEnlistment enlistment = CommonEnlistVolatile(
enlistmentNotification,
enlistmentOptions,
oletxTransaction
);
return enlistment;
}
internal IPromotedEnlistment EnlistVolatile(
IEnlistmentNotificationInternal enlistmentNotification,
EnlistmentOptions enlistmentOptions,
OletxTransaction oletxTransaction
)
{
IPromotedEnlistment enlistment = CommonEnlistVolatile(
enlistmentNotification,
enlistmentOptions,
oletxTransaction
);
return enlistment;
}
internal void Commit()
{
try
{
this.transactionShim.Commit();
}
catch (COMException comException)
{
if ( ( NativeMethods.XACT_E_ABORTED == comException.ErrorCode ) ||
( NativeMethods.XACT_E_INDOUBT == comException.ErrorCode )
)
{
Interlocked.CompareExchange<Exception>( ref this.innerException, comException, null );
if ( DiagnosticTrace.Verbose )
{
ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
comException );
}
}
else if ( NativeMethods.XACT_E_ALREADYINPROGRESS == comException.ErrorCode )
{
throw TransactionException.Create( SR.GetString( SR.TraceSourceOletx ), SR.GetString( SR.TransactionAlreadyOver ), comException );
}
else
{
OletxTransactionManager.ProxyException( comException );
throw;
}
}
}
internal void Rollback()
{
System.Guid tempGuid = Guid.Empty;
lock (this)
{
// if status is not active and not aborted, then throw an exception
if (TransactionStatus.Aborted != status &&
TransactionStatus.Active != status)
{
throw TransactionException.Create(
SR.GetString( SR.TraceSourceOletx ),
SR.GetString( SR.TransactionAlreadyOver ),
null,
this.DistributedTxId
);
}
// If the transaciton is already aborted, we can get out now. Calling Rollback on an already aborted transaction
// is legal.
if ( TransactionStatus.Aborted == status )
{
return;
}
// If there are still undecided enlistments, we can doom the transaction.
// We can safely make this check because we ALWAYS have a Phase1 Volatile enlistment to
// get the outcome. If we didn't have that enlistment, we would not be able to do this
// because not all instances of RealOletxTransaction would have enlistments.
if ( 0 < this.undecidedEnlistmentCount )
{
this.doomed = true;
}
else if ( this.tooLateForEnlistments )
{
// It's too late for rollback to be called here.
throw TransactionException.Create(
SR.GetString( SR.TraceSourceOletx ),
SR.GetString( SR.TransactionAlreadyOver ),
null,
this.DistributedTxId
);
}
// Tell the volatile enlistment containers to vote no now if they have outstanding
// notifications.
if ( null != this.phase0EnlistVolatilementContainerList )
{
foreach ( OletxPhase0VolatileEnlistmentContainer phase0VolatileContainer in this.phase0EnlistVolatilementContainerList )
{
phase0VolatileContainer.RollbackFromTransaction();
}
}
if ( null != this.phase1EnlistVolatilementContainer )
{
this.phase1EnlistVolatilementContainer.RollbackFromTransaction();
}
}
try
{
this.transactionShim.Abort();
}
catch (COMException comException)
{
// If the ErrorCode is XACT_E_ALREADYINPROGRESS and the transaciton is already doomed, we must be
// the root transaction and we have already called Commit - ignore the exception. The
// Rollback is allowed and one of the enlistments that hasn't voted yet will make sure it is
// aborted.
if ( NativeMethods.XACT_E_ALREADYINPROGRESS == comException.ErrorCode )
{
if ( this.doomed )
{
if ( DiagnosticTrace.Verbose )
{
ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
comException );
}
}
else
{
throw TransactionException.Create( SR.GetString( SR.TraceSourceOletx ),
SR.GetString( SR.TransactionAlreadyOver ),
comException,
this.DistributedTxId
);
}
}
else
{
// Otherwise, throw the exception out to the app.
OletxTransactionManager.ProxyException( comException );
throw;
}
}
}
internal void OletxTransactionCreated()
{
Interlocked.Increment( ref this.undisposedOletxTransactionCount );
}
internal void OletxTransactionDisposed()
{
int localCount = Interlocked.Decrement( ref this.undisposedOletxTransactionCount );
Debug.Assert( 0 <= localCount, "RealOletxTransction.undisposedOletxTransationCount < 0" );
}
internal void FireOutcome(TransactionStatus statusArg)
{
lock ( this )
{
if (statusArg == TransactionStatus.Committed)
{
if ( DiagnosticTrace.Verbose )
{
TransactionCommittedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
this.TransactionTraceId
);
}
status = TransactionStatus.Committed;
}
else if (statusArg == TransactionStatus.Aborted)
{
if ( DiagnosticTrace.Warning )
{
TransactionAbortedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
this.TransactionTraceId
);
}
status = TransactionStatus.Aborted;
}
else
{
if ( DiagnosticTrace.Warning )
{
TransactionInDoubtTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
this.TransactionTraceId
);
}
status = TransactionStatus.InDoubt;
}
}
// Let the InternalTransaciton know about the outcome.
if ( null != this.InternalTransaction )
{
InternalTransaction.DistributedTransactionOutcome( this.InternalTransaction, status );
}
}
internal TransactionTraceIdentifier TransactionTraceId
{
get
{
if ( TransactionTraceIdentifier.Empty == this.traceIdentifier )
{
lock ( this )
{
if ( TransactionTraceIdentifier.Empty == this.traceIdentifier )
{
if ( Guid.Empty != this.txGuid )
{
TransactionTraceIdentifier temp = new TransactionTraceIdentifier( this.txGuid.ToString(), 0 );
Thread.MemoryBarrier();
this.traceIdentifier = temp;
}
else
{
// We don't have a txGuid if we couldn't determine the guid of the
// transaction because the transaction was already committed or aborted before the RealOletxTransaction was
// created. If that happens, we don't want to throw just because we are trying to trace. So just use the
// TransactionTraceIdentifier.Empty.
}
}
}
}
return this.traceIdentifier;
}
}
internal void TMDown()
{
lock ( this )
{
// Tell the volatile enlistment containers that the TM went down.
if ( null != this.phase0EnlistVolatilementContainerList )
{
foreach ( OletxPhase0VolatileEnlistmentContainer phase0VolatileContainer in this.phase0EnlistVolatilementContainerList )
{
phase0VolatileContainer.TMDown();
}
}
}
// Tell the outcome enlistment the TM went down. We are doing this outside the lock
// because this may end up making calls out to user code through enlistments.
outcomeEnlistment.TMDown();
}
}
internal sealed class OutcomeEnlistment
{
private WeakReference weakRealTransaction;
private System.Guid txGuid;
private bool haveIssuedOutcome;
private TransactionStatus savedStatus;
internal Guid TransactionIdentifier
{
get
{
return txGuid;
}
}
internal OutcomeEnlistment()
{
this.haveIssuedOutcome = false;
this.savedStatus = TransactionStatus.InDoubt;
}
internal void SetRealTransaction( RealOletxTransaction realTx )
{
bool localHaveIssuedOutcome = false;
TransactionStatus localStatus = TransactionStatus.InDoubt;
lock ( this )
{
localHaveIssuedOutcome = this.haveIssuedOutcome;
localStatus = this.savedStatus;
// We want to do this while holding the lock.
if ( ! localHaveIssuedOutcome )
{
// We don't use MemoryBarrier here because all access to these member variables is done while holding
// a lock on the object.
// We are going to use a weak reference so the transaction object can get garbage
// collected before we receive the outcome.
this.weakRealTransaction = new WeakReference(realTx);
// Save the transaction guid so that the transaction can be removed from the
// TransactionTable
this.txGuid = realTx.TxGuid;
}
}
// We want to do this outside the lock because we are potentially calling out to user code.
if ( localHaveIssuedOutcome )
{
realTx.FireOutcome( localStatus );
// We may be getting this notification while there are still volatile prepare notifications outstanding. Tell the
// container to drive the aborted notification in that case.
if ( ( ( TransactionStatus.Aborted == localStatus ) || ( TransactionStatus.InDoubt == localStatus ) ) &&
( null != realTx.phase1EnlistVolatilementContainer ) )
{
realTx.phase1EnlistVolatilementContainer.OutcomeFromTransaction( localStatus );
}
}
}
internal void UnregisterOutcomeCallback()
{
this.weakRealTransaction = null;
}
private void InvokeOutcomeFunction(TransactionStatus status)
{
WeakReference localTxWeakRef = null;
// In the face of TMDown notifications, we may have already issued
// the outcome of the transaction.
lock ( this )
{
if ( this.haveIssuedOutcome )
{
return;
}
this.haveIssuedOutcome = true;
this.savedStatus = status;
localTxWeakRef = this.weakRealTransaction;
}
// It is possible for the weakRealTransaction member to be null if some exception was thrown
// during the RealOletxTransaction constructor after the OutcomeEnlistment object was created.
// In the finally block of the constructor, it calls UnregisterOutcomeCallback, which will
// null out weakRealTransaction. If this is the case, there is nothing to do.
if ( null != localTxWeakRef )
{
RealOletxTransaction realOletxTransaction = localTxWeakRef.Target as RealOletxTransaction;
if (null != realOletxTransaction)
{
realOletxTransaction.FireOutcome(status);
// The container list won't be changing on us now because the transaction status has changed such that
// new enlistments will not be created.
// Tell the Phase0Volatile containers, if any, about the outcome of the transaction.
// I am not protecting the access to phase0EnlistVolatilementContainerList with a lock on "this"
// because it is too late for these to be allocated anyway.
if ( null != realOletxTransaction.phase0EnlistVolatilementContainerList )
{
foreach ( OletxPhase0VolatileEnlistmentContainer phase0VolatileContainer in realOletxTransaction.phase0EnlistVolatilementContainerList )
{
phase0VolatileContainer.OutcomeFromTransaction( status );
}
}
// We may be getting this notification while there are still volatile prepare notifications outstanding. Tell the
// container to drive the aborted notification in that case.
if ( ( ( TransactionStatus.Aborted == status ) || ( TransactionStatus.InDoubt == status ) ) &&
( null != realOletxTransaction.phase1EnlistVolatilementContainer ) )
{
realOletxTransaction.phase1EnlistVolatilementContainer.OutcomeFromTransaction( status );
}
}
localTxWeakRef.Target = null;
}
}
//
// We need to figure out if the transaction is InDoubt as a result of TMDown. This
// can happen for a number of reasons. For instance we have responded prepared
// to all of our enlistments or we have no enlistments.
//
internal bool TransactionIsInDoubt(RealOletxTransaction realTx)
{
if ( null != realTx.committableTransaction &&
!realTx.committableTransaction.CommitCalled )
{
// If this is a committable transaction and commit has not been called
// then we know the outcome.
return false;
}
return realTx.UndecidedEnlistments == 0;
}
internal void TMDown()
{
// Assume that we don't know because that is the safest answer.
bool transactionIsInDoubt = true;
RealOletxTransaction realOletxTransaction = null;
lock ( this )
{
if ( null != this.weakRealTransaction )
{
realOletxTransaction = this.weakRealTransaction.Target as RealOletxTransaction;
}
}
if (null != realOletxTransaction)
{
lock ( realOletxTransaction )
{
transactionIsInDoubt = TransactionIsInDoubt(realOletxTransaction);
}
}
// If we have already voted, then we can't tell what the outcome
// is. We do this outside the lock because it may end up invoking user
// code when it calls into the enlistments later on the stack.
if ( transactionIsInDoubt )
{
this.InDoubt();
}
// We have not yet voted, so just say it aborted.
else
{
this.Aborted();
}
}
#region ITransactionOutcome Members
public void Committed()
{
InvokeOutcomeFunction(TransactionStatus.Committed);
return;
}
public void Aborted()
{
InvokeOutcomeFunction(TransactionStatus.Aborted);
return;
}
public void InDoubt()
{
InvokeOutcomeFunction(TransactionStatus.InDoubt);
return;
}
#endregion
}
}
|