//----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- namespace System.ServiceModel.Dispatcher { using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Runtime; using System.Runtime.CompilerServices; using System.ServiceModel; using System.ServiceModel.Activation; using System.ServiceModel.Channels; using System.ServiceModel.Diagnostics; using System.ServiceModel.Diagnostics.Application; using System.Transactions; class TransactionBehavior { bool isConcurrent; IsolationLevel isolation = ServiceBehaviorAttribute.DefaultIsolationLevel; DispatchRuntime dispatch; TimeSpan timeout = TimeSpan.Zero; bool isTransactedReceiveChannelDispatcher = false; internal TransactionBehavior() { } internal TransactionBehavior(DispatchRuntime dispatch) { this.isConcurrent = (dispatch.ConcurrencyMode == ConcurrencyMode.Multiple || dispatch.ConcurrencyMode == ConcurrencyMode.Reentrant); this.dispatch = dispatch; this.isTransactedReceiveChannelDispatcher = dispatch.ChannelDispatcher.IsTransactedReceive; // Don't pull in System.Transactions.dll if we don't need it if (dispatch.ChannelDispatcher.TransactionIsolationLevelSet) { this.InitializeIsolationLevel(dispatch); } this.timeout = TransactionBehavior.NormalizeTimeout(dispatch.ChannelDispatcher.TransactionTimeout); } internal static Exception CreateFault(string reasonText, string codeString, bool isNetDispatcherFault) { string faultCodeNamespace, action; // 'Transactions' action should be used only when we expect to have a TransactionChannel in the channel stack // otherwise one should use the NetDispatch action. if (isNetDispatcherFault) { faultCodeNamespace = FaultCodeConstants.Namespaces.NetDispatch; action = FaultCodeConstants.Actions.NetDispatcher; } else { faultCodeNamespace = FaultCodeConstants.Namespaces.Transactions; action = FaultCodeConstants.Actions.Transactions; } FaultReason reason = new FaultReason(reasonText, CultureInfo.CurrentCulture); FaultCode code = FaultCode.CreateSenderFaultCode(codeString, faultCodeNamespace); return new FaultException(reason, code, action); } internal static TransactionBehavior CreateIfNeeded(DispatchRuntime dispatch) { if (TransactionBehavior.NeedsTransactionBehavior(dispatch)) { return new TransactionBehavior(dispatch); } else { return null; } } internal static TimeSpan NormalizeTimeout(TimeSpan timeout) { if (TimeSpan.Zero == timeout) { timeout = TransactionManager.DefaultTimeout; } else if (TimeSpan.Zero != TransactionManager.MaximumTimeout && timeout > TransactionManager.MaximumTimeout) { timeout = TransactionManager.MaximumTimeout; } return timeout; } internal static CommittableTransaction CreateTransaction(IsolationLevel isolation, TimeSpan timeout) { TransactionOptions options = new TransactionOptions(); options.IsolationLevel = isolation; options.Timeout = timeout; return new CommittableTransaction(options); } internal void SetCurrent(ref MessageRpc rpc) { if (!this.isConcurrent) { rpc.InstanceContext.Transaction.SetCurrent(ref rpc); } } internal void ResolveOutcome(ref MessageRpc rpc) { if ((rpc.InstanceContext != null) && (rpc.transaction != null)) { TransactionInstanceContextFacet context = rpc.InstanceContext.Transaction; if (context != null) { context.CheckIfTxCompletedAndUpdateAttached(ref rpc, this.isConcurrent); } rpc.Transaction.Complete(rpc.Error); } } Transaction GetInstanceContextTransaction(ref MessageRpc rpc) { return rpc.InstanceContext.Transaction.Attached; } [MethodImpl(MethodImplOptions.NoInlining)] void InitializeIsolationLevel(DispatchRuntime dispatch) { this.isolation = dispatch.ChannelDispatcher.TransactionIsolationLevel; } static bool NeedsTransactionBehavior(DispatchRuntime dispatch) { DispatchOperation unhandled = dispatch.UnhandledDispatchOperation; if ((unhandled != null) && (unhandled.TransactionRequired)) { return true; } if (dispatch.ChannelDispatcher.IsTransactedReceive) //check if we have transacted receive { return true; } for (int i = 0; i < dispatch.Operations.Count; i++) { DispatchOperation operation = dispatch.Operations[i]; if (operation.TransactionRequired) { return true; } } return false; } internal void ResolveTransaction(ref MessageRpc rpc) { if (rpc.Operation.HasDefaultUnhandledActionInvoker) { // we ignore unhandled operations return; } Transaction contextTransaction = null; //If we are inside a TransactedReceiveScope in workflow, then we need to look into the PPD and not the InstanceContext //to get the contextTransaction if (rpc.Operation.IsInsideTransactedReceiveScope) { // We may want to use an existing transaction for the instance. IInstanceTransaction instanceTransaction = rpc.Operation.Invoker as IInstanceTransaction; if (instanceTransaction != null) { contextTransaction = instanceTransaction.GetTransactionForInstance(rpc.OperationContext); } if (contextTransaction != null) { if (DiagnosticUtility.ShouldTraceInformation) { TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.TxSourceTxScopeRequiredUsingExistingTransaction, SR.GetString(SR.TraceCodeTxSourceTxScopeRequiredUsingExistingTransaction, contextTransaction.TransactionInformation.LocalIdentifier, rpc.Operation.Name) ); } } } else { contextTransaction = this.GetInstanceContextTransaction(ref rpc); } Transaction transaction = null; try { transaction = TransactionMessageProperty.TryGetTransaction(rpc.Request); } catch (TransactionException e) { DiagnosticUtility.TraceHandledException(e, TraceEventType.Error); throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(TransactionBehavior.CreateFault(SR.GetString(SR.SFxTransactionUnmarshalFailed, e.Message), FaultCodeConstants.Codes.TransactionUnmarshalingFailed, false)); } if (rpc.Operation.TransactionRequired) { if (transaction != null) { if (this.isTransactedReceiveChannelDispatcher) { if (DiagnosticUtility.ShouldTraceInformation) { TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.TxSourceTxScopeRequiredIsTransactedTransport, SR.GetString(SR.TraceCodeTxSourceTxScopeRequiredIsTransactedTransport, transaction.TransactionInformation.LocalIdentifier, rpc.Operation.Name) ); } } else { if (DiagnosticUtility.ShouldTraceInformation) { TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.TxSourceTxScopeRequiredIsTransactionFlow, SR.GetString(SR.TraceCodeTxSourceTxScopeRequiredIsTransactionFlow, transaction.TransactionInformation.LocalIdentifier, rpc.Operation.Name) ); } if (PerformanceCounters.PerformanceCountersEnabled) { PerformanceCounters.TxFlowed(PerformanceCounters.GetEndpointDispatcher(), rpc.Operation.Name); } bool sameTransaction = false; if (rpc.Operation.IsInsideTransactedReceiveScope) { sameTransaction = transaction.Equals(contextTransaction); } else { sameTransaction = transaction == contextTransaction; } if (!sameTransaction) { try { transaction = transaction.DependentClone(DependentCloneOption.RollbackIfNotComplete); } catch (TransactionException e) { DiagnosticUtility.TraceHandledException(e, TraceEventType.Error); throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(TransactionBehavior.CreateFault(SR.GetString(SR.SFxTransactionAsyncAborted), FaultCodeConstants.Codes.TransactionAborted, true)); } } } } } else { // We got a transaction from the ChannelHandler. // Transport is transacted. // But operation doesn't require the transaction, so no one ever will commit it. // Because of that we have to commit it here. if (transaction != null && this.isTransactedReceiveChannelDispatcher) { try { if (null != rpc.TransactedBatchContext) { rpc.TransactedBatchContext.ForceCommit(); rpc.TransactedBatchContext = null; } else { TransactionInstanceContextFacet.Complete(transaction, null); } } finally { transaction.Dispose(); transaction = null; } } } InstanceContext context = rpc.InstanceContext; if (context.Transaction.ShouldReleaseInstance && !this.isConcurrent) { if (context.Behavior.ReleaseServiceInstanceOnTransactionComplete) { context.ReleaseServiceInstance(); if (DiagnosticUtility.ShouldTraceInformation) { TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.TxReleaseServiceInstanceOnCompletion, SR.GetString(SR.TraceCodeTxReleaseServiceInstanceOnCompletion, contextTransaction.TransactionInformation.LocalIdentifier) ); } } context.Transaction.ShouldReleaseInstance = false; if (transaction == null || transaction == contextTransaction) { rpc.Transaction.Current = contextTransaction; throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(TransactionBehavior.CreateFault(SR.GetString(SR.SFxTransactionAsyncAborted), FaultCodeConstants.Codes.TransactionAborted, true)); } else { contextTransaction = null; } } if (rpc.Operation.TransactionRequired) { if (transaction == null) { if (contextTransaction != null) { transaction = contextTransaction; if (DiagnosticUtility.ShouldTraceInformation) { TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.TxSourceTxScopeRequiredIsAttachedTransaction, SR.GetString(SR.TraceCodeTxSourceTxScopeRequiredIsAttachedTransaction, transaction.TransactionInformation.LocalIdentifier, rpc.Operation.Name) ); } } else { transaction = TransactionBehavior.CreateTransaction(this.isolation, this.timeout); if (DiagnosticUtility.ShouldTraceInformation) { TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.TxSourceTxScopeRequiredIsCreateNewTransaction, SR.GetString(SR.TraceCodeTxSourceTxScopeRequiredIsCreateNewTransaction, transaction.TransactionInformation.LocalIdentifier, rpc.Operation.Name) ); } } } if ((this.isolation != IsolationLevel.Unspecified) && (transaction.IsolationLevel != this.isolation)) { throw TraceUtility.ThrowHelperError(TransactionBehavior.CreateFault (SR.GetString(SR.IsolationLevelMismatch2, transaction.IsolationLevel, this.isolation), FaultCodeConstants.Codes.TransactionIsolationLevelMismatch, false), rpc.Request); } rpc.Transaction.Current = transaction; rpc.InstanceContext.Transaction.AddReference(ref rpc, rpc.Transaction.Current, true); try { rpc.Transaction.Clone = transaction.Clone(); if (rpc.Operation.IsInsideTransactedReceiveScope) { //It is because we want to synchronize the dispatcher processing of messages with the commit //processing that is started by the completion of a TransactedReceiveScope. We need to make sure //that all the dispatcher processing is done and we can do that by creating a blocking dependent clone and only //completing it after all of the message processing is done for a given TransactionRpcFacet rpc.Transaction.CreateDependentClone(); } } catch (ObjectDisposedException e)//transaction may be async aborted { DiagnosticUtility.TraceHandledException(e, TraceEventType.Error); throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(TransactionBehavior.CreateFault(SR.GetString(SR.SFxTransactionAsyncAborted), FaultCodeConstants.Codes.TransactionAborted, true)); } rpc.InstanceContext.Transaction.AddReference(ref rpc, rpc.Transaction.Clone, false); rpc.OperationContext.TransactionFacet = rpc.Transaction; if (!rpc.Operation.TransactionAutoComplete) { rpc.Transaction.SetIncomplete(); } } } internal void InitializeCallContext(ref MessageRpc rpc) { if (rpc.Operation.TransactionRequired) { rpc.Transaction.ThreadEnter(ref rpc.Error); } } internal void ClearCallContext(ref MessageRpc rpc) { if (rpc.Operation.TransactionRequired) { rpc.Transaction.ThreadLeave(); } } } internal class TransactionRpcFacet { //internal members // Current is the original transaction that we created/flowed/whatever. This is // the "current" transaction used by the operation, and we keep it around so we // can commit it, complete it, etc. // // Clone is a clone of Current. We keep it around to pass into TransactionScope // so that System.Transactions.Transaction.Current is not CommittableTransaction // or anything dangerous like that. internal Transaction Current; internal Transaction Clone; internal DependentTransaction dependentClone; internal bool IsCompleted = true; internal MessageRpc rpc; TransactionScope scope; bool transactionSetComplete = false; // To track if user has called SetTransactionComplete() internal TransactionRpcFacet() { } internal TransactionRpcFacet(ref MessageRpc rpc) { this.rpc = rpc; } // Calling Complete will Commit or Abort the transaction based on, // error - If any user error is propagated to the service we abort the transaction unless SetTransactionComplete was successful. // transactionDoomed - If internal error occurred and this error may or may not be propagated // by the user to the service. Abort the Tx if transactionDoomed is set true. // // If the user violates the following rules, the transaction is doomed. // User cannot call TransactionSetComplete() when TransactionAutoComplete is true. // User cannot call TransactionSetComplete() multiple times. [MethodImpl(MethodImplOptions.NoInlining)] internal void Complete(Exception error) { if (!object.ReferenceEquals(this.Current, null)) { TransactedBatchContext batchContext = this.rpc.TransactedBatchContext; if (null != batchContext) { if (null == error) { batchContext.Complete(); } else { batchContext.ForceRollback(); } batchContext.InDispatch = false; } else { if (this.transactionSetComplete) { // Commit the transaction when TransactionSetComplete() is called and // even when an exception(non transactional) happens after this call. rpc.InstanceContext.Transaction.CompletePendingTransaction(this.Current, null); if (DiagnosticUtility.ShouldTraceInformation) { TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.TxCompletionStatusCompletedForSetComplete, SR.GetString(SR.TraceCodeTxCompletionStatusCompletedForSetComplete, this.Current.TransactionInformation.LocalIdentifier, this.rpc.Operation.Name) ); } } else if (this.IsCompleted || (error != null)) { rpc.InstanceContext.Transaction.CompletePendingTransaction(this.Current, error); } } if (this.rpc.Operation.IsInsideTransactedReceiveScope) { //We are done with the message processing associated with this TransactionRpcFacet so a commit that may have //been started by a TransactedReceiveScope can move forward. this.CompleteDependentClone(); } this.Current = null; } } internal void SetIncomplete() { this.IsCompleted = false; } internal void Completed() { if (this.scope == null) { return; } // Prohibit user from calling SetTransactionComplete() when TransactionAutoComplete is set to true. // Transaction will be aborted. if (this.rpc.Operation.TransactionAutoComplete) { try { this.Current.Rollback(); } catch (ObjectDisposedException e) { //we don't want to mask the real error here DiagnosticUtility.TraceHandledException(e, TraceEventType.Error); } throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR.GetString(SR.SFxTransactionInvalidSetTransactionComplete, rpc.Operation.Name, rpc.Host.Description.Name))); } // Prohibit user from calling SetTransactionComplete() multiple times. // Transaction will be aborted. else if (this.transactionSetComplete) { try { this.Current.Rollback(); } catch (ObjectDisposedException e) { //we don't want to mask the real error here DiagnosticUtility.TraceHandledException(e, TraceEventType.Error); } throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR.GetString(SR.SFxMultiSetTransactionComplete, rpc.Operation.Name, rpc.Host.Description.Name))); } this.transactionSetComplete = true; this.IsCompleted = true; this.scope.Complete(); } [MethodImpl(MethodImplOptions.NoInlining)] internal void ThreadEnter(ref Exception error) { Transaction clone = this.Clone; if ((clone != null) && (error == null)) { if (TD.TransactionScopeCreateIsEnabled()) { if (clone != null && clone.TransactionInformation != null) { TD.TransactionScopeCreate(rpc.EventTraceActivity, clone.TransactionInformation.LocalIdentifier, clone.TransactionInformation.DistributedIdentifier); } } this.scope = this.rpc.InstanceContext.Transaction.CreateTransactionScope(clone); this.transactionSetComplete = false; } } [MethodImpl(MethodImplOptions.NoInlining)] internal void ThreadLeave() { if (this.scope != null) { if (!this.transactionSetComplete) { this.scope.Complete(); } try { this.scope.Dispose(); this.scope = null; } catch (TransactionException e) { DiagnosticUtility.TraceHandledException(e, TraceEventType.Error); throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(TransactionBehavior.CreateFault(SR.GetString(SR.SFxTransactionAsyncAborted), FaultCodeConstants.Codes.TransactionAborted, true)); } } } internal void CreateDependentClone() { if ((this.dependentClone == null) && (this.Clone != null)) { this.dependentClone = this.Clone.DependentClone(DependentCloneOption.BlockCommitUntilComplete); } } internal void CompleteDependentClone() { if (this.dependentClone != null) { this.dependentClone.Complete(); } } } internal sealed class TransactionInstanceContextFacet { internal Transaction waiting; // waiting to become Single because Single is on his way out. internal Transaction Attached; IResumeMessageRpc paused; // the IResumeMessageRpc for this.waiting. object mutex; Transaction current; // the one true transaction when Concurrency=false. InstanceContext instanceContext; Dictionary pending; // When Concurrency=true, all the still pending guys. bool shouldReleaseInstance = false; internal TransactionInstanceContextFacet(InstanceContext instanceContext) { this.instanceContext = instanceContext; this.mutex = instanceContext.ThisLock; } // ........................................................................................................ // no need to lock the following property because it's used only if Concurrency = false internal bool ShouldReleaseInstance { get { return this.shouldReleaseInstance; } set { this.shouldReleaseInstance = value; } } // ........................................................................................................ [MethodImpl(MethodImplOptions.NoInlining)] internal void CheckIfTxCompletedAndUpdateAttached(ref MessageRpc rpc, bool isConcurrent) { if (rpc.Transaction.Current == null) { return; } lock (this.mutex) { if (!isConcurrent) { if (this.shouldReleaseInstance) { this.shouldReleaseInstance = false; if (rpc.Error == null) //we don't want to mask the initial error { rpc.Error = TransactionBehavior.CreateFault(SR.GetString(SR.SFxTransactionAsyncAborted), FaultCodeConstants.Codes.TransactionAborted, true); DiagnosticUtility.TraceHandledException(rpc.Error, TraceEventType.Error); if (DiagnosticUtility.ShouldTraceInformation) { TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.TxCompletionStatusCompletedForAsyncAbort, SR.GetString(SR.TraceCodeTxCompletionStatusCompletedForAsyncAbort, rpc.Transaction.Current.TransactionInformation.LocalIdentifier, rpc.Operation.Name) ); } } } if (rpc.Transaction.IsCompleted || (rpc.Error != null)) { if (DiagnosticUtility.ShouldTraceInformation) { if (rpc.Error != null) { TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.TxCompletionStatusCompletedForError, SR.GetString(SR.TraceCodeTxCompletionStatusCompletedForError, rpc.Transaction.Current.TransactionInformation.LocalIdentifier, rpc.Operation.Name) ); } else { TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.TxCompletionStatusCompletedForAutocomplete, SR.GetString(SR.TraceCodeTxCompletionStatusCompletedForAutocomplete, rpc.Transaction.Current.TransactionInformation.LocalIdentifier, rpc.Operation.Name) ); } } this.Attached = null; if (!(waiting == null)) { // tx processing requires failfast when state is inconsistent DiagnosticUtility.FailFast("waiting should be null when resetting current"); } this.current = null; } else { this.Attached = rpc.Transaction.Current; if (DiagnosticUtility.ShouldTraceInformation) { TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.TxCompletionStatusRemainsAttached, SR.GetString(SR.TraceCodeTxCompletionStatusRemainsAttached, rpc.Transaction.Current.TransactionInformation.LocalIdentifier, rpc.Operation.Name) ); } } } else if (!this.pending.ContainsKey(rpc.Transaction.Current)) { //transaction has been asynchronously aborted if (rpc.Error == null) //we don't want to mask the initial error { rpc.Error = TransactionBehavior.CreateFault(SR.GetString(SR.SFxTransactionAsyncAborted), FaultCodeConstants.Codes.TransactionAborted, true); DiagnosticUtility.TraceHandledException(rpc.Error, TraceEventType.Error); if (DiagnosticUtility.ShouldTraceInformation) { TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.TxCompletionStatusCompletedForAsyncAbort, SR.GetString(SR.TraceCodeTxCompletionStatusCompletedForAsyncAbort, rpc.Transaction.Current.TransactionInformation.LocalIdentifier, rpc.Operation.Name) ); } } } } } // ........................................................................................................ internal void CompletePendingTransaction(Transaction transaction, Exception error) { lock (this.mutex) { if (this.pending.ContainsKey(transaction)) { Complete(transaction, error); } } } // ........................................................................................................ internal static void Complete(Transaction transaction, Exception error) { try { if (error == null) { CommittableTransaction commit = (transaction as CommittableTransaction); if (commit != null) { commit.Commit(); } else { DependentTransaction complete = (transaction as DependentTransaction); if (complete != null) { complete.Complete(); } } } else { transaction.Rollback(); } } catch (TransactionException e) { DiagnosticUtility.TraceHandledException(e, TraceEventType.Error); throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(TransactionBehavior.CreateFault(SR.GetString(SR.SFxTransactionAsyncAborted), FaultCodeConstants.Codes.TransactionAborted, true)); } } // ........................................................................................................ internal TransactionScope CreateTransactionScope(Transaction transaction) { lock (this.mutex) { if (this.pending.ContainsKey(transaction)) { try { return new TransactionScope(transaction); } catch (TransactionException e) { DiagnosticUtility.TraceHandledException(e, TraceEventType.Error); //we'll rethrow below } } } //the transaction was asynchronously aborted throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(TransactionBehavior.CreateFault(SR.GetString(SR.SFxTransactionAsyncAborted), FaultCodeConstants.Codes.TransactionAborted, true)); } // ........................................................................................................ internal void SetCurrent(ref MessageRpc rpc) { Transaction requestTransaction = rpc.Transaction.Current; if (!(requestTransaction != null)) { // tx processing requires failfast when state is inconsistent DiagnosticUtility.FailFast("we should never get here with a requestTransaction null"); } lock (this.mutex) { if (this.current == null) { this.current = requestTransaction; } else if (this.current != requestTransaction) { this.waiting = requestTransaction; this.paused = rpc.Pause(); } else { rpc.Transaction.Current = this.current; //rpc.Transaction.Current should get the dependent clone } } } // ........................................................................................................ internal void AddReference(ref MessageRpc rpc, Transaction tx, bool updateCallCount) { lock (this.mutex) { if (this.pending == null) { this.pending = new Dictionary(); } if (tx != null) { if (this.pending == null) { this.pending = new Dictionary(); } RemoveReferenceRM rm; if (!this.pending.TryGetValue(tx, out rm)) { RemoveReferenceRM rrm = new RemoveReferenceRM(this.instanceContext, tx, rpc.Operation.Name); rrm.CallCount = 1; this.pending.Add(tx, rrm); } else if (updateCallCount) { rm.CallCount += 1; } } } } internal void RemoveReference(Transaction tx) { lock (this.mutex) { if (tx.Equals(this.current)) { if (this.waiting != null) { this.current = waiting; this.waiting = null; if (instanceContext.Behavior.ReleaseServiceInstanceOnTransactionComplete) { instanceContext.ReleaseServiceInstance(); if (DiagnosticUtility.ShouldTraceInformation) { TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.TxReleaseServiceInstanceOnCompletion, SR.GetString(SR.TraceCodeTxReleaseServiceInstanceOnCompletion, tx.TransactionInformation.LocalIdentifier) ); } } bool alreadyResumedNoLock; this.paused.Resume(out alreadyResumedNoLock); if (alreadyResumedNoLock) { Fx.Assert("TransactionBehavior resumed more than once for same call."); } } else { this.shouldReleaseInstance = true; this.current = null; } } if (this.pending != null) { if (this.pending.ContainsKey(tx)) { this.pending.Remove(tx); } } } } // ........................................................................................................ abstract class VolatileBase : ISinglePhaseNotification { protected InstanceContext InstanceContext; protected Transaction Transaction; protected VolatileBase(InstanceContext instanceContext, Transaction transaction) { this.InstanceContext = instanceContext; this.Transaction = transaction; this.Transaction.EnlistVolatile(this, EnlistmentOptions.None); } protected abstract void Completed(); public virtual void Commit(Enlistment enlistment) { this.Completed(); } public virtual void InDoubt(Enlistment enlistment) { this.Completed(); } public virtual void Rollback(Enlistment enlistment) { this.Completed(); } public virtual void SinglePhaseCommit(SinglePhaseEnlistment enlistment) { enlistment.Committed(); this.Completed(); } public void Prepare(PreparingEnlistment preparingEnlistment) { preparingEnlistment.Prepared(); } } sealed class RemoveReferenceRM : VolatileBase { string operation; long callCount = 0; EndpointDispatcher endpointDispatcher; internal RemoveReferenceRM(InstanceContext instanceContext, Transaction tx, string operation) : base(instanceContext, tx) { this.operation = operation; if (PerformanceCounters.PerformanceCountersEnabled) { this.endpointDispatcher = PerformanceCounters.GetEndpointDispatcher(); } AspNetEnvironment.Current.IncrementBusyCount(); if (AspNetEnvironment.Current.TraceIncrementBusyCountIsEnabled()) { AspNetEnvironment.Current.TraceIncrementBusyCount(this.GetType().FullName); } } internal long CallCount { get { return this.callCount; } set { this.callCount = value; } } protected override void Completed() { this.InstanceContext.Transaction.RemoveReference(this.Transaction); AspNetEnvironment.Current.DecrementBusyCount(); if (AspNetEnvironment.Current.TraceDecrementBusyCountIsEnabled()) { AspNetEnvironment.Current.TraceDecrementBusyCount(this.GetType().FullName); } } public override void SinglePhaseCommit(SinglePhaseEnlistment enlistment) { if (PerformanceCounters.PerformanceCountersEnabled) { PerformanceCounters.TxCommitted(this.endpointDispatcher, CallCount); } base.SinglePhaseCommit(enlistment); } public override void Commit(Enlistment enlistment) { if (PerformanceCounters.PerformanceCountersEnabled) { PerformanceCounters.TxCommitted(this.endpointDispatcher, CallCount); } base.Commit(enlistment); } public override void Rollback(Enlistment enlistment) { if (PerformanceCounters.PerformanceCountersEnabled) { PerformanceCounters.TxAborted(this.endpointDispatcher, CallCount); } if (DiagnosticUtility.ShouldTraceInformation) { TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.TxAsyncAbort, SR.GetString(SR.TraceCodeTxAsyncAbort, this.Transaction.TransactionInformation.LocalIdentifier) ); } base.Rollback(enlistment); } public override void InDoubt(Enlistment enlistment) { if (PerformanceCounters.PerformanceCountersEnabled) { PerformanceCounters.TxInDoubt(this.endpointDispatcher, CallCount); } base.InDoubt(enlistment); } } } internal enum ExclusiveInstanceContextTransactionResult { Acquired, Wait, Fault }; }