File: System\ServiceModel\Dispatcher\TransactionBehavior.cs
Project: ndp\cdf\src\WCF\ServiceModel\System.ServiceModel.csproj (System.ServiceModel)
//-----------------------------------------------------------------------------
// 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<Transaction, RemoveReferenceRM> 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<Transaction, RemoveReferenceRM>();
                }
 
                if (tx != null)
                {
                    if (this.pending == null)
                    {
                        this.pending = new Dictionary<Transaction, RemoveReferenceRM>();
                    }
 
                    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
    };
}