Imported Upstream version 4.0.0~alpha1

Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
This commit is contained in:
Jo Shields
2015-04-07 09:35:12 +01:00
parent 283343f570
commit 3c1f479b9d
22469 changed files with 2931443 additions and 869343 deletions

View File

@@ -0,0 +1,11 @@
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
namespace System.ServiceModel.Activities.Dispatcher
{
enum BufferedReceiveState
{
WaitingOnInstance,
WaitingOnBookmark
}
}

View File

@@ -0,0 +1,408 @@
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
namespace System.ServiceModel.Activities.Dispatcher
{
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime;
using System.Runtime.Collections;
using System.Runtime.Diagnostics;
using System.Runtime.DurableInstancing;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Diagnostics;
using System.ServiceModel.Dispatcher;
using System.Xml.Linq;
using System.Text;
using SR2 = System.ServiceModel.Activities.SR;
class CorrelationKeyCalculator
{
MessageBufferCalculator bufferCalculator;
MessageCalculator messageCalculator;
XName scopeName;
MessageFilterTable<SelectRuntime> whereRuntime;
CorrelationKeyCache keyCache;
public CorrelationKeyCalculator(XName scopeName)
{
this.whereRuntime = new MessageFilterTable<SelectRuntime>();
this.scopeName = scopeName;
this.keyCache = new CorrelationKeyCache();
}
public void AddQuery(MessageFilter where, MessageQueryTable<string> select,
IDictionary<string, MessageQueryTable<string>> selectAdditional, bool isContextQuery)
{
SelectRuntime selectRuntime = new SelectRuntime { Select = select, SelectAdditional = selectAdditional, IsContextQuery = isContextQuery };
this.whereRuntime.Add(where, selectRuntime);
}
public bool CalculateKeys(Message message, out InstanceKey instanceKey,
out ICollection<InstanceKey> additionalKeys)
{
MessageCalculator calculator = this.messageCalculator;
if (calculator == null)
{
calculator = this.messageCalculator = new MessageCalculator(this);
}
return calculator.CalculateKeys(message, null, out instanceKey, out additionalKeys);
}
public bool CalculateKeys(MessageBuffer buffer, Message messageToReadHeaders, out InstanceKey instanceKey,
out ICollection<InstanceKey> additionalKeys)
{
MessageBufferCalculator calculator = this.bufferCalculator;
if (calculator == null)
{
calculator = this.bufferCalculator = new MessageBufferCalculator(this);
}
return calculator.CalculateKeys(buffer, messageToReadHeaders, out instanceKey, out additionalKeys);
}
abstract class Calculator<T>
{
CorrelationKeyCalculator parent;
public Calculator(CorrelationKeyCalculator parent)
{
this.parent = parent;
}
public bool CalculateKeys(T target, Message messageToReadHeaders, out InstanceKey instanceKey,
out ICollection<InstanceKey> additionalKeys)
{
SelectRuntime select;
instanceKey = InstanceKey.InvalidKey;
additionalKeys = null;
// this is a query on the serverside, either Receive or SendReply
// Where
if (!this.ExecuteWhere(target, messageToReadHeaders, this.parent.whereRuntime, out select))
{
return false;
}
Dictionary<string, string> values = new Dictionary<string, string>();
// Select
if (select.Select.Count > 0)
{
bool allOptional = true;
foreach (KeyValuePair<MessageQuery, string> result in this.ExecuteSelect(target, messageToReadHeaders, select.Select, select.IsContextQuery))
{
if (!(result.Key is OptionalMessageQuery))
{
allOptional = false;
}
if (!string.IsNullOrEmpty(result.Value))
{
values.Add(select.Select[result.Key], result.Value);
}
}
if (values.Count == 0)
{
if (!allOptional)
{
throw FxTrace.Exception.AsError(new ProtocolException(SR2.EmptyCorrelationQueryResults));
}
}
else
{
instanceKey = this.GetInstanceKey(values);
if (TD.TraceCorrelationKeysIsEnabled())
{
TraceCorrelationKeys(instanceKey, values);
}
}
}
// SelectAdditional
foreach (KeyValuePair<string, MessageQueryTable<string>> item in select.SelectAdditional)
{
if (additionalKeys == null)
{
additionalKeys = new List<InstanceKey>();
}
values.Clear();
InstanceKey additionalKey = InstanceKey.InvalidKey;
bool allOptional = true;
foreach (KeyValuePair<MessageQuery, string> result in this.ExecuteSelect(target, messageToReadHeaders, item.Value, select.IsContextQuery))
{
if (!(result.Key is OptionalMessageQuery))
{
allOptional = false;
}
if (!string.IsNullOrEmpty(result.Value))
{
values.Add(item.Value[result.Key], result.Value);
}
}
if (values.Count == 0)
{
if (!allOptional)
{
throw FxTrace.Exception.AsError(new ProtocolException(SR2.EmptyCorrelationQueryResults));
}
}
else
{
additionalKey = new CorrelationKey(values, this.parent.scopeName.ToString(), null) { Name = item.Key };
if (TD.TraceCorrelationKeysIsEnabled())
{
TraceCorrelationKeys(additionalKey, values);
}
}
additionalKeys.Add(additionalKey);
}
return true;
}
CorrelationKey GetInstanceKey(Dictionary<string, string> values)
{
// We only optimize for upto 3 keys
if (values.Count <= 3)
{
CorrelationKey correlationKey;
CorrelationCacheKey cacheKey = CorrelationCacheKey.CreateKey(values);
if (this.parent.keyCache.TryGetValue(cacheKey, out correlationKey))
{
return correlationKey;
}
correlationKey = new CorrelationKey(values, this.parent.scopeName.ToString(), null);
this.parent.keyCache.Add(cacheKey, correlationKey);
return correlationKey;
}
return new CorrelationKey(values, this.parent.scopeName.ToString(), null);
}
protected abstract IEnumerable<KeyValuePair<MessageQuery, string>> ExecuteSelect(T target, Message messageToReadHeaders,
MessageQueryTable<string> select, bool IsContextQuery);
protected abstract bool ExecuteWhere(T target, Message messageToReadHeaders, MessageFilterTable<SelectRuntime> whereRuntime,
out SelectRuntime select);
void TraceCorrelationKeys(InstanceKey instanceKey, Dictionary<string, string> values)
{
StringBuilder keyValueAsString = new StringBuilder();
foreach (KeyValuePair<string, string> pair in values)
{
keyValueAsString.Append(pair.Key).Append(":").Append(pair.Value).Append(',');
}
TD.TraceCorrelationKeys(instanceKey.Value, keyValueAsString.ToString(), this.parent.scopeName.ToString());
}
}
class MessageBufferCalculator : Calculator<MessageBuffer>
{
public MessageBufferCalculator(CorrelationKeyCalculator parent)
: base(parent)
{
}
protected override IEnumerable<KeyValuePair<MessageQuery, string>> ExecuteSelect(MessageBuffer target, Message messageToReadHeaders,
MessageQueryTable<string> select, bool isContextQuery)
{
if (isContextQuery && messageToReadHeaders != null)
{
//we can pass in the message directly in this case since we know it is a context query that will read from the header
return select.Evaluate<string>(messageToReadHeaders);
}
else
{
return select.Evaluate<string>(target);
}
}
protected override bool ExecuteWhere(MessageBuffer target, Message messageToReadHeaders, MessageFilterTable<SelectRuntime> whereRuntime,
out SelectRuntime select)
{
return whereRuntime.GetMatchingValue(target, messageToReadHeaders, out select);
}
}
[SuppressMessage(FxCop.Category.Performance, FxCop.Rule.AvoidUncalledPrivateCode,
Justification = "Will use this once correlation with streaming is fixed")]
class MessageCalculator : Calculator<Message>
{
[SuppressMessage(FxCop.Category.Performance, FxCop.Rule.AvoidUncalledPrivateCode,
Justification = "Will use this once correlation with streaming is fixed")]
public MessageCalculator(CorrelationKeyCalculator parent)
: base(parent)
{
}
protected override IEnumerable<KeyValuePair<MessageQuery, string>> ExecuteSelect(Message target, Message messageToReadHeaders,
MessageQueryTable<string> select, bool isContextQuery)
{
return select.Evaluate<string>(target);
}
protected override bool ExecuteWhere(Message target, Message messageToReadHeaders, MessageFilterTable<SelectRuntime> whereRuntime,
out SelectRuntime select)
{
// messageToReadHeaders is not used in case of MessageCalculator
return whereRuntime.GetMatchingValue(target, out select);
}
}
class SelectRuntime
{
public MessageQueryTable<string> Select { get; set; }
public IDictionary<string, MessageQueryTable<string>> SelectAdditional { get; set; }
internal bool IsContextQuery { get; set; }
}
// Needs to seperate from the generic calculator as all jitted types
// should share the same cache.
class CorrelationKeyCache
{
HopperCache cache;
object cacheLock;
internal CorrelationKeyCache()
{
this.cache = new HopperCache(128, false);
this.cacheLock = new object();
}
internal void Add(CorrelationCacheKey key, CorrelationKey value)
{
Fx.Assert(key != null, "Cannot add a null CorrelationCacheKey to the cache.");
lock (this.cacheLock)
{
this.cache.Add(key, value);
}
}
internal bool TryGetValue(CorrelationCacheKey key, out CorrelationKey value)
{
value = (CorrelationKey)this.cache.GetValue(this.cacheLock, key);
return (value != null);
}
}
abstract class CorrelationCacheKey
{
static internal CorrelationCacheKey CreateKey(Dictionary<string, string> keys)
{
if (keys.Count == 1)
{
return new SingleCacheKey(keys);
}
else
{
return new MultipleCacheKey(keys);
}
}
static int CombineHashCodes(int h1, int h2)
{
return (((h1 << 5) + h1) ^ h2);
}
class SingleCacheKey : CorrelationCacheKey
{
int hashCode;
string key;
string value;
public SingleCacheKey(Dictionary<string, string> keys)
{
Fx.Assert(keys.Count == 1, "Cannot intialize CorrelationCacheSingleKey with multiple key values.");
foreach (KeyValuePair<string, string> keyValue in keys)
{
this.key = keyValue.Key;
this.value = keyValue.Value;
this.hashCode = CombineHashCodes(this.key.GetHashCode(), this.value.GetHashCode());
return;
}
}
public override bool Equals(object obj)
{
SingleCacheKey target = obj as SingleCacheKey;
return (target != null &&
(this.hashCode == target.hashCode) &&
((this.key == target.key) && (this.value == target.value)));
}
public override int GetHashCode()
{
return this.hashCode;
}
}
class MultipleCacheKey : CorrelationCacheKey
{
Dictionary<string, string> keyValues;
int hashCode;
public MultipleCacheKey(Dictionary<string, string> keys)
{
this.keyValues = keys;
foreach (KeyValuePair<string, string> keyValue in this.keyValues)
{
int hash1 = CombineHashCodes(this.hashCode, keyValue.Key.GetHashCode());
this.hashCode = CombineHashCodes(hash1, keyValue.Value.GetHashCode());
}
}
public override bool Equals(object obj)
{
MultipleCacheKey target = obj as MultipleCacheKey;
if (target != null)
{
if ((this.hashCode == target.hashCode) &&
(this.keyValues.Count == target.keyValues.Count))
{
string sourceValue;
foreach (KeyValuePair<string, string> targetKeyValue in target.keyValues)
{
if (!this.keyValues.TryGetValue(targetKeyValue.Key, out sourceValue) ||
sourceValue != targetKeyValue.Value)
{
return false;
}
}
//All keys and values are the same
return true;
}
}
return false;
}
public override int GetHashCode()
{
return this.hashCode;
}
}
}
}
}

View File

@@ -0,0 +1,52 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace System.ServiceModel.Activities.Dispatcher
{
using System.Globalization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Xml;
using SR2 = System.ServiceModel.Activities.SR;
class DurableDispatcherAddressingFault : MessageFault
{
FaultCode faultCode;
FaultReason faultReason;
public DurableDispatcherAddressingFault()
{
this.faultCode = FaultCode.CreateSenderFaultCode(XD2.ContextHeader.MissingContextHeader, XD2.ContextHeader.Namespace);
this.faultReason = new FaultReason(new FaultReasonText(SR2.CurrentOperationCannotCreateInstance, CultureInfo.CurrentCulture));
}
public override FaultCode Code
{
get
{
return this.faultCode;
}
}
public override bool HasDetail
{
get
{
return false;
}
}
public override FaultReason Reason
{
get
{
return this.faultReason;
}
}
protected override void OnWriteDetailContents(XmlDictionaryWriter writer)
{
throw FxTrace.Exception.AsError(new NotImplementedException());
}
}
}

View File

@@ -0,0 +1,44 @@
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
namespace System.ServiceModel.Activities.Dispatcher
{
using System.Runtime;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
//This is a perchannel instance context provider which returns non-throttled IC.
class DurableInstanceContextProvider : IInstanceContextProvider
{
ServiceHostBase serviceHostBase;
public DurableInstanceContextProvider(ServiceHostBase serviceHost)
{
this.serviceHostBase = serviceHost;
}
public InstanceContext GetExistingInstanceContext(Message message, IContextChannel channel)
{
Fx.Assert(message != null, "Null message");
Fx.Assert(channel != null, "Null channel");
return new InstanceContext(this.serviceHostBase);
}
public void InitializeInstanceContext(InstanceContext instanceContext, Message message, IContextChannel channel)
{
}
public bool IsIdle(InstanceContext instanceContext)
{
return true;
}
public void NotifyIdle(InstanceContextIdleCallback callback, InstanceContext instanceContext)
{
//Empty
}
}
}

View File

@@ -0,0 +1,49 @@
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
namespace System.ServiceModel.Activities.Dispatcher
{
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
class DurableInstanceProvider : IInstanceProvider
{
object singletonDurableInstance;
ServiceHostBase serviceHost;
public DurableInstanceProvider(ServiceHostBase serviceHost)
{
this.serviceHost = serviceHost;
}
//Dummy Instance stuffed onto InstanceContext
object Instance
{
get
{
if (singletonDurableInstance == null)
{
singletonDurableInstance = new object();
}
return singletonDurableInstance;
}
}
public object GetInstance(InstanceContext instanceContext, Message message)
{
return this.Instance;
}
public object GetInstance(InstanceContext instanceContext)
{
return this.Instance;
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
}
}
}

View File

@@ -0,0 +1,157 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace System.ServiceModel.Activities.Dispatcher
{
using System.Globalization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Xml;
using SR2 = System.ServiceModel.Activities.SR;
class OperationExecutionFault : MessageFault
{
static FaultCode instanceAbortedCode;
static FaultCode instanceCompletedCode;
static FaultCode instanceTerminatedCode;
static FaultCode instanceSuspendedFaultCode;
static FaultCode instanceUnloadedFaultCode;
static FaultCode instanceNotFoundCode;
static FaultCode instanceLockedFaultCode;
static FaultCode operationNotAvailableFaultCode;
static FaultCode updatedFailedFaultCode;
FaultCode faultCode;
FaultReason faultReason;
OperationExecutionFault(string description, FaultCode subcode)
{
this.faultCode = FaultCode.CreateSenderFaultCode(subcode);
this.faultReason = new FaultReason(new FaultReasonText(
description, CultureInfo.CurrentCulture));
}
public override FaultCode Code
{
get
{
return this.faultCode;
}
}
public override bool HasDetail
{
get
{
return false;
}
}
public override FaultReason Reason
{
get
{
return this.faultReason;
}
}
public static OperationExecutionFault CreateTransactedLockException(Guid instanceId, string operationName)
{
if (instanceLockedFaultCode == null)
{
instanceLockedFaultCode = new FaultCode(XD2.WorkflowControlServiceFaults.InstanceLockedUnderTransaction, XD2.WorkflowServices.Namespace);
}
return new OperationExecutionFault(SR2.InstanceLockedUnderTransaction(operationName, instanceId), instanceLockedFaultCode);
}
public static OperationExecutionFault CreateInstanceUnloadedFault(string description)
{
if (instanceUnloadedFaultCode == null)
{
instanceUnloadedFaultCode = new FaultCode(XD2.WorkflowControlServiceFaults.InstanceUnloaded, XD2.WorkflowServices.Namespace);
}
return new OperationExecutionFault(description, instanceUnloadedFaultCode);
}
public static OperationExecutionFault CreateInstanceNotFoundFault(string description)
{
if (instanceNotFoundCode == null)
{
instanceNotFoundCode = new FaultCode(XD2.WorkflowControlServiceFaults.InstanceNotFound, XD2.WorkflowServices.Namespace);
}
return new OperationExecutionFault(description, instanceNotFoundCode);
}
public static OperationExecutionFault CreateCompletedFault(string description)
{
if (instanceCompletedCode == null)
{
instanceCompletedCode = new FaultCode(XD2.WorkflowControlServiceFaults.InstanceCompleted, XD2.WorkflowServices.Namespace);
}
return new OperationExecutionFault(description, instanceCompletedCode);
}
public static OperationExecutionFault CreateTerminatedFault(string description)
{
if (instanceTerminatedCode == null)
{
instanceTerminatedCode = new FaultCode(XD2.WorkflowControlServiceFaults.InstanceTerminated, XD2.WorkflowServices.Namespace);
}
return new OperationExecutionFault(description, instanceTerminatedCode);
}
public static OperationExecutionFault CreateSuspendedFault(Guid instanceId, string operationName)
{
if (instanceSuspendedFaultCode == null)
{
instanceSuspendedFaultCode = new FaultCode(XD2.WorkflowControlServiceFaults.InstanceSuspended, XD2.WorkflowServices.Namespace);
}
return new OperationExecutionFault(SR2.InstanceSuspended(operationName, instanceId), instanceSuspendedFaultCode);
}
public static OperationExecutionFault CreateOperationNotAvailableFault(Guid instanceId, string operationName)
{
if (operationNotAvailableFaultCode == null)
{
operationNotAvailableFaultCode = new FaultCode(XD2.WorkflowControlServiceFaults.OperationNotAvailable, XD2.WorkflowServices.Namespace);
}
return new OperationExecutionFault(SR2.OperationNotAvailable(operationName, instanceId), operationNotAvailableFaultCode);
}
public static OperationExecutionFault CreateAbortedFault(string description)
{
if (instanceAbortedCode == null)
{
instanceAbortedCode = new FaultCode(XD2.WorkflowControlServiceFaults.InstanceAborted, XD2.WorkflowServices.Namespace);
}
return new OperationExecutionFault(description, instanceAbortedCode);
}
public static OperationExecutionFault CreateUpdateFailedFault(string description)
{
if (updatedFailedFaultCode == null)
{
updatedFailedFaultCode = new FaultCode(XD2.WorkflowControlServiceFaults.UpdateFailed, XD2.WorkflowServices.Namespace);
}
return new OperationExecutionFault(description, updatedFailedFaultCode);
}
public static bool IsAbortedFaultException(FaultException exception)
{
if (exception.Code != null && exception.Code.SubCode != null &&
exception.Code.SubCode.Name == instanceAbortedCode.Name && exception.Code.SubCode.Namespace == instanceAbortedCode.Namespace)
{
return true;
}
else
{
return false;
}
}
protected override void OnWriteDetailContents(XmlDictionaryWriter writer)
{
throw FxTrace.Exception.AsError(new NotImplementedException());
}
}
}

View File

@@ -0,0 +1,32 @@
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
namespace System.ServiceModel.Activities.Dispatcher
{
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
class OptionalMessageQuery : MessageQuery
{
public OptionalMessageQuery()
: base()
{
}
public MessageQuery Query
{
get;
set;
}
public override TResult Evaluate<TResult>(MessageBuffer buffer)
{
return this.Query.Evaluate<TResult>(buffer);
}
public override TResult Evaluate<TResult>(Message message)
{
return this.Query.Evaluate<TResult>(message);
}
}
}

View File

@@ -0,0 +1,228 @@
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
namespace System.ServiceModel.Activities.Dispatcher
{
using System.Collections.Generic;
using System.Runtime;
using System.Threading;
using System.Transactions;
sealed class PersistenceContextEnlistment : IEnlistmentNotification
{
PreparingEnlistment preparingEnlistment;
Enlistment enlistment;
// This will be true if we have received either a Prepare or Rollback
// notification from the transaction manager. If this is true, it is too
// late to try to add more entries to the undo collection.
bool tooLateForMoreUndo;
Transaction transaction;
object ThisLock = new object();
List<PersistenceContext> enlistedContexts;
static Action<object> prepareCallback;
static Action<object> commitCallback;
static Action<object> rollbackCallback;
static Action<object> indoubtCallback;
internal PersistenceContextEnlistment(PersistenceContext context, Transaction transaction)
{
this.transaction = transaction;
this.enlistedContexts = new List<PersistenceContext>();
this.enlistedContexts.Add(context);
}
internal void AddToEnlistment(PersistenceContext context)
{
lock (this.ThisLock)
{
if (tooLateForMoreUndo)
{
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.PersistenceTooLateToEnlist));
}
this.enlistedContexts.Add(context);
}
}
internal static Action<object> PrepareCallback
{
get
{
if (prepareCallback == null)
{
prepareCallback = new Action<object>(DoPrepare);
}
return prepareCallback;
}
}
internal static Action<object> CommitCallback
{
get
{
if (commitCallback == null)
{
commitCallback = new Action<object>(DoCommit);
}
return commitCallback;
}
}
internal static Action<object> RollbackCallback
{
get
{
if (rollbackCallback == null)
{
rollbackCallback = new Action<object>(DoRollback);
}
return rollbackCallback;
}
}
internal static Action<object> IndoubtCallback
{
get
{
if (indoubtCallback == null)
{
indoubtCallback = new Action<object>(DoIndoubt);
}
return indoubtCallback;
}
}
void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
{
// We don't want to try to grab one of our locks while executing on the
// System.Transactions notification thread because that will block all
// the other notifications that need to be made. So schedule the
// processing of this on another thread. If we decide that the locks
// aren't necessary, we can get rid of this.
this.preparingEnlistment = preparingEnlistment;
ActionItem.Schedule(PrepareCallback, this);
}
void IEnlistmentNotification.Commit(Enlistment enlistment)
{
// We don't want to try to grab one of our locks while executing on the
// System.Transactions notification thread because that will block all
// the other notifications that need to be made. So schedule the
// processing of this on another thread. If we decide that the locks
// aren't necessary, we can get rid of this.
this.enlistment = enlistment;
ActionItem.Schedule(CommitCallback, this);
}
void IEnlistmentNotification.Rollback(Enlistment enlistment)
{
// We don't want to try to grab one of our locks while executing on the
// System.Transactions notification thread because that will block all
// the other notifications that need to be made. So schedule the
// processing of this on another thread. If we decide that the locks
// aren't necessary, we can get rid of this.
this.enlistment = enlistment;
ActionItem.Schedule(RollbackCallback, this);
}
void IEnlistmentNotification.InDoubt(Enlistment enlistment)
{
// We don't want to try to grab one of our locks while executing on the
// System.Transactions notification thread because that will block all
// the other notifications that need to be made. So schedule the
// processing of this on another thread. If we decide that the locks
// aren't necessary, we can get rid of this.
this.enlistment = enlistment;
ActionItem.Schedule(IndoubtCallback, this);
}
internal static void DoPrepare(object state)
{
PersistenceContextEnlistment pcEnlist = state as PersistenceContextEnlistment;
Fx.Assert(null != pcEnlist, "PersistenceContextEnlistment.DoPrepare called with an object that is not a PersistenceContext.");
lock (pcEnlist.ThisLock)
{
pcEnlist.tooLateForMoreUndo = true;
}
// This needs to be done outside of the lock because it could induce System.Transactions
// to do a whole bunch of other work inline, including issuing the Commit call and doing
// Completion notifications for hte transaction. We don't want to be holding the lock
// during all of that.
pcEnlist.preparingEnlistment.Prepared();
}
internal static void DoCommit(object state)
{
PersistenceContextEnlistment pcEnlist = state as PersistenceContextEnlistment;
Fx.Assert(null != pcEnlist, "PersistenceContextEnlistment.DoCommit called with an object that is not a PersistenceContext.");
lock (pcEnlist.ThisLock)
{
// Wake up the next waiter for the pc, if any.
foreach (PersistenceContext context in pcEnlist.enlistedContexts)
{
context.ScheduleNextTransactionWaiter();
}
}
lock (PersistenceContext.Enlistments)
{
PersistenceContext.Enlistments.Remove(pcEnlist.transaction.GetHashCode());
}
// This needs to be outside the lock because SysTx might do other stuff on the thread.
pcEnlist.enlistment.Done();
}
internal static void DoRollback(object state)
{
PersistenceContextEnlistment pcEnlist = state as PersistenceContextEnlistment;
Fx.Assert(null != pcEnlist, "PersistenceContextEnlistment.DoRollback called with an object that is not a PersistenceContext.");
lock (pcEnlist.ThisLock)
{
pcEnlist.tooLateForMoreUndo = true;
foreach (PersistenceContext context in pcEnlist.enlistedContexts)
{
context.Abort();
context.ScheduleNextTransactionWaiter();
}
}
lock (PersistenceContext.Enlistments)
{
PersistenceContext.Enlistments.Remove(pcEnlist.transaction.GetHashCode());
}
// This needs to be outside the lock because SysTx might do other stuff on the thread.
pcEnlist.enlistment.Done();
}
internal static void DoIndoubt(object state)
{
PersistenceContextEnlistment pcEnlist = state as PersistenceContextEnlistment;
Fx.Assert(null != pcEnlist, "PersistenceContextEnlistment.DoIndoubt called with an object that is not a PersistenceContext.");
lock (pcEnlist.ThisLock)
{
pcEnlist.tooLateForMoreUndo = true;
foreach (PersistenceContext context in pcEnlist.enlistedContexts)
{
context.Abort();
context.ScheduleNextTransactionWaiter();
}
}
lock (PersistenceContext.Enlistments)
{
PersistenceContext.Enlistments.Remove(pcEnlist.transaction.GetHashCode());
}
// This needs to be outside the lock because SysTx might do other stuff on the thread.
pcEnlist.enlistment.Done();
}
}
}

View File

@@ -0,0 +1,13 @@
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
namespace System.ServiceModel.Activities.Dispatcher
{
enum SaveStatus
{
Locked = 0,
Unlocked,
Completed,
}
}

View File

@@ -0,0 +1,169 @@
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
namespace System.ServiceModel.Activities.Dispatcher
{
using System.Runtime;
using System.Transactions;
//1) On Tx.Prepare
// Persist the instance.
// When Persist completes Tx.Prepared called.
// When Persist fails Tx.ForceRollback called.
//2) On Tx.Commit
// DurableInstance.OnTransactionCompleted().
//3) On Tx.Abort
// DurableInstance.OnTransactionAborted()
class TransactionContext : IEnlistmentNotification
{
static AsyncCallback handleEndPrepare = Fx.ThunkCallback(new AsyncCallback(HandleEndPrepare));
Transaction currentTransaction;
WorkflowServiceInstance durableInstance;
public TransactionContext(WorkflowServiceInstance durableInstance, Transaction currentTransaction)
{
Fx.Assert(durableInstance != null, "Null DurableInstance passed to TransactionContext.");
Fx.Assert(currentTransaction != null, "Null Transaction passed to TransactionContext.");
this.currentTransaction = currentTransaction.Clone();
this.durableInstance = durableInstance;
this.currentTransaction.EnlistVolatile(this, EnlistmentOptions.EnlistDuringPrepareRequired);
}
public Transaction CurrentTransaction
{
get
{
return this.currentTransaction;
}
}
void IEnlistmentNotification.Commit(Enlistment enlistment)
{
enlistment.Done();
this.durableInstance.TransactionCommitted();
}
void IEnlistmentNotification.InDoubt(Enlistment enlistment)
{
enlistment.Done();
Fx.Assert(this.currentTransaction.TransactionInformation.Status == TransactionStatus.InDoubt, "Transaction state should be InDoubt at this point");
TransactionException exception = this.GetAbortedOrInDoubtTransactionException();
Fx.Assert(exception != null, "Need a valid TransactionException at this point");
this.durableInstance.OnTransactionAbortOrInDoubt(exception);
}
void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
{
bool success = false;
try
{
IAsyncResult result = new PrepareAsyncResult(this, TransactionContext.handleEndPrepare, preparingEnlistment);
if (result.CompletedSynchronously)
{
PrepareAsyncResult.End(result);
preparingEnlistment.Prepared();
}
success = true;
}
//we need to swollow the TransactionException as it could because another party aborting it
catch (TransactionException)
{
}
finally
{
if (!success)
{
preparingEnlistment.ForceRollback();
}
}
}
void IEnlistmentNotification.Rollback(Enlistment enlistment)
{
enlistment.Done();
Fx.Assert(this.currentTransaction.TransactionInformation.Status == TransactionStatus.Aborted, "Transaction state should be Aborted at this point");
TransactionException exception = this.GetAbortedOrInDoubtTransactionException();
Fx.Assert(exception != null, "Need a valid TransactionException at this point");
this.durableInstance.OnTransactionAbortOrInDoubt(exception);
}
TransactionException GetAbortedOrInDoubtTransactionException()
{
try
{
TransactionHelper.ThrowIfTransactionAbortedOrInDoubt(this.currentTransaction);
}
catch (TransactionException exception)
{
return exception;
}
return null;
}
static void HandleEndPrepare(IAsyncResult result)
{
PreparingEnlistment preparingEnlistment = (PreparingEnlistment)result.AsyncState;
bool success = false;
try
{
if (!result.CompletedSynchronously)
{
PrepareAsyncResult.End(result);
preparingEnlistment.Prepared();
}
success = true;
}
//we need to swollow the TransactionException as it could because another party aborting it
catch (TransactionException)
{
}
finally
{
if (!success)
{
preparingEnlistment.ForceRollback();
}
}
}
class PrepareAsyncResult : TransactedAsyncResult
{
static readonly AsyncCompletion onEndPersist = new AsyncCompletion(OnEndPersist);
readonly TransactionContext context;
public PrepareAsyncResult(TransactionContext context, AsyncCallback callback, object state)
: base(callback, state)
{
this.context = context;
IAsyncResult result = null;
using (PrepareTransactionalCall(this.context.currentTransaction))
{
result = this.context.durableInstance.BeginPersist(TimeSpan.MaxValue, PrepareAsyncCompletion(PrepareAsyncResult.onEndPersist), this);
}
if (SyncContinue(result))
{
Complete(true);
}
}
public static void End(IAsyncResult result)
{
AsyncResult.End<PrepareAsyncResult>(result);
}
static bool OnEndPersist(IAsyncResult result)
{
PrepareAsyncResult thisPtr = (PrepareAsyncResult)result.AsyncState;
thisPtr.context.durableInstance.EndPersist(result);
thisPtr.context.durableInstance.OnTransactionPrepared();
return true;
}
}
}
}

View File

@@ -0,0 +1,199 @@
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
namespace System.ServiceModel.Activities.Dispatcher
{
using System.Runtime;
using System.Transactions;
using System.Threading;
sealed class TransactionWaitAsyncResult : AsyncResult
{
static Action<object> timerCallback;
DependentTransaction dependentTransaction;
IOThreadTimer timer;
[Fx.Tag.SynchronizationObject(Blocking = false)]
object thisLock;
internal TransactionWaitAsyncResult(Transaction transaction, PersistenceContext persistenceContext, TimeSpan timeout, AsyncCallback callback, object state)
: base(callback, state)
{
bool completeSelf = false;
TransactionException exception = null;
this.PersistenceContext = persistenceContext;
this.thisLock = new object();
if (null != transaction)
{
// We want an "blocking" dependent transaction because we want to ensure the transaction
// does not commit successfully while we are still waiting in the queue for the PC transaction
// lock.
this.dependentTransaction = transaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
}
else
{
this.dependentTransaction = null;
}
// Put a lock around this and Complete() in case the transaction we are queueing up behind
// finishes and we end up calling Complete() before we actually finish constructing this
// object by creating the DependentClone and setting up the IOThreadTimer.
lock (ThisLock)
{
if (persistenceContext.QueueForTransactionLock(transaction, this))
{
// If we were given a transaction in our constructor, we need to
// create a volatile enlistment on it and complete the
// dependent clone that we created. This will allow the transaction to commit
// successfully when the time comes.
if (null != transaction)
{
// We are not going async, so we need to complete our dependent clone now.
this.dependentTransaction.Complete();
exception = this.CreateVolatileEnlistment(transaction);
}
completeSelf = true;
}
else
{
// If the timeout value is not TimeSpan.MaxValue, start a timer.
if (timeout != TimeSpan.MaxValue)
{
this.timer = new IOThreadTimer(TimeoutCallbackAction, this, true);
this.timer.Set(timeout);
}
}
}
// We didn't want to call Complete while holding the lock.
if (completeSelf)
{
base.Complete(true, exception);
}
}
internal PersistenceContext PersistenceContext { get; set; }
internal Transaction Transaction
{
get
{
return this.dependentTransaction;
}
}
object ThisLock
{
get
{
return this.thisLock;
}
}
internal static Action<object> TimeoutCallbackAction
{
get
{
if (timerCallback == null)
{
timerCallback = new Action<object>(TimeoutCallback);
}
return timerCallback;
}
}
// Returns true if this TransactionWaitAsyncResult was completed and has NOT timed out.
// Returns false if this TransactionWaitAsyncResult has timed out.
internal bool Complete()
{
Exception exception = null;
// Lock to prevent completion while we are still in the process of constructing this object.
lock (ThisLock)
{
// If we have a timer, but it has already expired, return false.
if ((this.timer != null) && (!this.timer.Cancel()))
{
return false;
}
// If we have a dependent transaction, complete it now.
if (this.dependentTransaction != null)
{
// If we were given a transaction in our constructor, we need to
// create a volatile enlistment on it and complete the
// dependent clone that we created. This will allow the transaction to commit
// successfully when the time comes.
exception = this.CreateVolatileEnlistment(this.dependentTransaction);
this.dependentTransaction.Complete();
}
}
// Indicate that we are complete.
Complete(false, exception);
return true;
}
TransactionException CreateVolatileEnlistment(Transaction transactionToEnlist)
{
TransactionException result = null;
PersistenceContextEnlistment enlistment = null;
int key = transactionToEnlist.GetHashCode();
lock (PersistenceContext.Enlistments)
{
try
{
if (!PersistenceContext.Enlistments.TryGetValue(key, out enlistment))
{
enlistment = new PersistenceContextEnlistment(this.PersistenceContext, transactionToEnlist);
transactionToEnlist.EnlistVolatile(enlistment, EnlistmentOptions.None);
// We don't save of the Enlistment object returned from EnlistVolatile. We don't need
// it here. When our PersistenceContextEnlistment object gets notified on Prepare,
// Commit, Rollback, or InDoubt, it is provided with the Enlistment object.
PersistenceContext.Enlistments.Add(key, enlistment);
}
else
{
enlistment.AddToEnlistment(this.PersistenceContext);
}
}
catch (TransactionException txException)
{
result = txException;
// We own the lock but failed to create enlistment. Manually wake up the next waiter.
// We only handle TransactionException, in case of other exception that failed to create enlistment,
// It will fallback to Timeout. This is safe to avoid multiple waiters owning same lock.
this.PersistenceContext.ScheduleNextTransactionWaiter();
}
}
return result;
}
static void TimeoutCallback(object state)
{
TransactionWaitAsyncResult thisPtr = (TransactionWaitAsyncResult)state;
Fx.Assert(null != thisPtr, "TransactionWaitAsyncResult.TimeoutCallback called with an object that is not a TransactionWaitAsyncResult.");
// As a general policy, we are not going to rollback the transaction because of this timeout. Instead, we are letting
// the caller make the decision to rollback or not based on exception we are throwing. It could be that they could
// tolerate the timeout and try something else and still commit the transaction.
if (thisPtr.dependentTransaction != null)
{
thisPtr.dependentTransaction.Complete();
}
thisPtr.Complete(false, new TimeoutException(SR.TransactionPersistenceTimeout));
}
public static void End(IAsyncResult result)
{
AsyncResult.End<TransactionWaitAsyncResult>(result);
}
}
}

View File

@@ -0,0 +1,45 @@
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
namespace System.ServiceModel.Activities.Dispatcher
{
using System.Threading;
class WorkflowSynchronizationContext : SynchronizationContext
{
static WorkflowSynchronizationContext singletonInstance;
WorkflowSynchronizationContext()
: base()
{
}
public static WorkflowSynchronizationContext Instance
{
get
{
if (singletonInstance == null)
{
singletonInstance = new WorkflowSynchronizationContext();
}
return singletonInstance;
}
}
public override SynchronizationContext CreateCopy()
{
return this;
}
public override void Post(SendOrPostCallback d, object state)
{
Send(d, state);
}
public override void Send(SendOrPostCallback d, object state)
{
base.Send(d, state);
}
}
}