e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1074 lines
43 KiB
C#
1074 lines
43 KiB
C#
//-----------------------------------------------------------------------------
|
|
// 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
|
|
};
|
|
}
|