|
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
namespace System.Activities
{
using System;
using System.Activities.DynamicUpdate;
using System.Activities.Runtime;
using System.Activities.Tracking;
using System.Activities.XamlIntegration;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime;
using System.Runtime.Serialization;
[DataContract(Name = XD.ActivityInstance.Name, Namespace = XD.Runtime.Namespace)]
[Fx.Tag.XamlVisible(false)]
public sealed class ActivityInstance : ActivityInstanceMap.IActivityReferenceWithEnvironment
{
Activity activity;
ChildList childList;
ReadOnlyCollection<ActivityInstance> childCache;
CompletionBookmark completionBookmark;
ActivityInstanceMap instanceMap;
ActivityInstance parent;
string ownerName;
int busyCount;
ExtendedData extendedData;
// most activities will have a symbol (either variable or argument, so optimize for that case)
bool noSymbols;
ActivityInstanceState state;
bool isCancellationRequested;
bool performingDefaultCancelation;
Substate substate;
long id;
bool initializationIncomplete;
// This is serialized through the SerializedEnvironment property
LocationEnvironment environment;
ExecutionPropertyManager propertyManager;
internal ActivityInstance(Activity activity)
{
this.activity = activity;
this.state = ActivityInstanceState.Executing;
this.substate = Substate.Created;
this.ImplementationVersion = activity.ImplementationVersion;
}
public Activity Activity
{
get
{
return this.activity;
}
internal set
{
Fx.Assert(value != null || this.state == ActivityInstanceState.Closed, "");
this.activity = value;
}
}
Activity ActivityInstanceMap.IActivityReference.Activity
{
get
{
return this.Activity;
}
}
internal Substate SubState
{
get
{
return this.substate;
}
}
[DataMember(EmitDefaultValue = false)]
internal LocationEnvironment SerializedEnvironment
{
get
{
if (this.IsCompleted)
{
return null;
}
else
{
return this.environment;
}
}
set
{
Fx.Assert(value != null, "We should never get null here.");
this.environment = value;
}
}
[DataMember(EmitDefaultValue = false, Name = "busyCount")]
internal int SerializedBusyCount
{
get { return this.busyCount; }
set { this.busyCount = value; }
}
[DataMember(EmitDefaultValue = false, Name = "extendedData")]
internal ExtendedData SerializedExtendedData
{
get { return this.extendedData; }
set { this.extendedData = value; }
}
[DataMember(EmitDefaultValue = false, Name = "noSymbols")]
internal bool SerializedNoSymbols
{
get { return this.noSymbols; }
set { this.noSymbols = value; }
}
[DataMember(EmitDefaultValue = false, Name = "state")]
internal ActivityInstanceState SerializedState
{
get { return this.state; }
set { this.state = value; }
}
[DataMember(EmitDefaultValue = false, Name = "isCancellationRequested")]
internal bool SerializedIsCancellationRequested
{
get { return this.isCancellationRequested; }
set { this.isCancellationRequested = value; }
}
[DataMember(EmitDefaultValue = false, Name = "performingDefaultCancelation")]
internal bool SerializedPerformingDefaultCancelation
{
get { return this.performingDefaultCancelation; }
set { this.performingDefaultCancelation = value; }
}
[DataMember(EmitDefaultValue = false, Name = "substate")]
internal Substate SerializedSubstate
{
get { return this.substate; }
set { this.substate = value; }
}
[DataMember(EmitDefaultValue = false, Name = "id")]
internal long SerializedId
{
get { return this.id; }
set { this.id = value; }
}
[DataMember(EmitDefaultValue = false, Name = "initializationIncomplete")]
internal bool SerializedInitializationIncomplete
{
get { return this.initializationIncomplete; }
set { this.initializationIncomplete = value; }
}
internal LocationEnvironment Environment
{
get
{
Fx.Assert(this.environment != null, "There should always be an environment");
return this.environment;
}
}
internal ActivityInstanceMap InstanceMap
{
get
{
return this.instanceMap;
}
}
public bool IsCompleted
{
get
{
return ActivityUtilities.IsCompletedState(this.State);
}
}
public ActivityInstanceState State
{
get
{
return this.state;
}
}
internal bool IsCancellationRequested
{
get
{
return this.isCancellationRequested;
}
set
{
// This is set at the time of scheduling the cancelation work item
Fx.Assert(!this.isCancellationRequested, "We should not set this if we have already requested cancel.");
Fx.Assert(value != false, "We should only set this to true.");
this.isCancellationRequested = value;
}
}
internal bool IsPerformingDefaultCancelation
{
get
{
return this.performingDefaultCancelation;
}
}
public string Id
{
get
{
return this.id.ToString(CultureInfo.InvariantCulture);
}
}
internal long InternalId
{
get
{
return this.id;
}
}
internal bool IsEnvironmentOwner
{
get
{
return !this.noSymbols;
}
}
internal bool IsResolvingArguments
{
get
{
return this.substate == Substate.ResolvingArguments;
}
}
internal bool HasNotExecuted
{
get
{
return (this.substate & Substate.PreExecuting) != 0;
}
}
internal bool HasPendingWork
{
get
{
if (this.HasChildren)
{
return true;
}
// check if we have pending bookmarks or outstanding OperationControlContexts/WorkItems
if (this.busyCount > 0)
{
return true;
}
return false;
}
}
internal bool OnlyHasOutstandingBookmarks
{
get
{
// If our whole busy count is because of blocking bookmarks then
// we should return true
return !this.HasChildren && this.extendedData != null && (this.extendedData.BlockingBookmarkCount == this.busyCount);
}
}
internal ActivityInstance Parent
{
get
{
return this.parent;
}
}
internal bool WaitingForTransactionContext
{
get
{
if (this.extendedData == null)
{
return false;
}
else
{
return this.extendedData.WaitingForTransactionContext;
}
}
set
{
EnsureExtendedData();
this.extendedData.WaitingForTransactionContext = value;
}
}
[DataMember(EmitDefaultValue = false)]
internal CompletionBookmark CompletionBookmark
{
get
{
return this.completionBookmark;
}
set
{
this.completionBookmark = value;
}
}
internal FaultBookmark FaultBookmark
{
get
{
if (this.extendedData == null)
{
return null;
}
return this.extendedData.FaultBookmark;
}
set
{
Fx.Assert(value != null || (this.extendedData == null || this.extendedData.FaultBookmark == null), "cannot go from non-null to null");
if (value != null)
{
EnsureExtendedData();
this.extendedData.FaultBookmark = value;
}
}
}
internal bool HasChildren
{
get
{
return (this.childList != null && this.childList.Count > 0);
}
}
internal ExecutionPropertyManager PropertyManager
{
get
{
return this.propertyManager;
}
set
{
this.propertyManager = value;
}
}
internal WorkflowDataContext DataContext
{
get
{
if (this.extendedData != null)
{
return this.extendedData.DataContext;
}
return null;
}
set
{
EnsureExtendedData();
this.extendedData.DataContext = value;
}
}
internal object CompiledDataContexts
{
get;
set;
}
internal object CompiledDataContextsForImplementation
{
get;
set;
}
internal bool HasActivityReferences
{
get
{
return this.extendedData != null && this.extendedData.HasActivityReferences;
}
}
[DataMember(Name = XD.ActivityInstance.PropertyManager, EmitDefaultValue = false)]
[SuppressMessage(FxCop.Category.Performance, FxCop.Rule.AvoidUncalledPrivateCode, Justification = "Called from Serialization")]
internal ExecutionPropertyManager SerializedPropertyManager
{
get
{
if (this.propertyManager == null || !this.propertyManager.ShouldSerialize(this))
{
return null;
}
else
{
return this.propertyManager;
}
}
set
{
Fx.Assert(value != null, "We don't emit the default value so this should never be null.");
this.propertyManager = value;
}
}
[DataMember(Name = XD.ActivityInstance.Children, EmitDefaultValue = false)]
internal ChildList SerializedChildren
{
get
{
if (this.HasChildren)
{
this.childList.Compress();
return this.childList;
}
return null;
}
set
{
Fx.Assert(value != null, "value from Serialization should not be null");
this.childList = value;
}
}
[DataMember(Name = XD.ActivityInstance.Owner, EmitDefaultValue = false)]
internal string OwnerName
{
get
{
if (this.ownerName == null)
{
this.ownerName = this.Activity.GetType().Name;
}
return this.ownerName;
}
set
{
Fx.Assert(value != null, "value from Serialization should not be null");
this.ownerName = value;
}
}
[DataMember(EmitDefaultValue = false)]
public Version ImplementationVersion
{
get;
internal set;
}
internal static ActivityInstance CreateCompletedInstance(Activity activity)
{
ActivityInstance instance = new ActivityInstance(activity);
instance.state = ActivityInstanceState.Closed;
return instance;
}
internal static ActivityInstance CreateCanceledInstance(Activity activity)
{
ActivityInstance instance = new ActivityInstance(activity);
instance.state = ActivityInstanceState.Canceled;
return instance;
}
internal ReadOnlyCollection<ActivityInstance> GetChildren()
{
if (!this.HasChildren)
{
return ChildList.Empty;
}
if (this.childCache == null)
{
this.childCache = this.childList.AsReadOnly();
}
return this.childCache;
}
internal HybridCollection<ActivityInstance> GetRawChildren()
{
return this.childList;
}
void EnsureExtendedData()
{
if (this.extendedData == null)
{
this.extendedData = new ExtendedData();
}
}
// Busy Count includes the following:
// 1. Active OperationControlContexts.
// 2. Active work items.
// 3. Blocking bookmarks.
internal void IncrementBusyCount()
{
this.busyCount++;
}
internal void DecrementBusyCount()
{
Fx.Assert(this.busyCount > 0, "something went wrong with our bookkeeping");
this.busyCount--;
}
internal void DecrementBusyCount(int amount)
{
Fx.Assert(this.busyCount >= amount, "something went wrong with our bookkeeping");
this.busyCount -= amount;
}
internal void AddActivityReference(ActivityInstanceReference reference)
{
EnsureExtendedData();
this.extendedData.AddActivityReference(reference);
}
internal void AddBookmark(Bookmark bookmark, BookmarkOptions options)
{
bool affectsBusyCount = false;
if (!BookmarkOptionsHelper.IsNonBlocking(options))
{
IncrementBusyCount();
affectsBusyCount = true;
}
EnsureExtendedData();
this.extendedData.AddBookmark(bookmark, affectsBusyCount);
}
internal void RemoveBookmark(Bookmark bookmark, BookmarkOptions options)
{
bool affectsBusyCount = false;
if (!BookmarkOptionsHelper.IsNonBlocking(options))
{
DecrementBusyCount();
affectsBusyCount = true;
}
Fx.Assert(this.extendedData != null, "something went wrong with our bookkeeping");
this.extendedData.RemoveBookmark(bookmark, affectsBusyCount);
}
internal void RemoveAllBookmarks(BookmarkScopeManager bookmarkScopeManager, BookmarkManager bookmarkManager)
{
if (this.extendedData != null)
{
this.extendedData.PurgeBookmarks(bookmarkScopeManager, bookmarkManager, this);
}
}
internal void SetInitializationIncomplete()
{
this.initializationIncomplete = true;
}
internal void MarkCanceled()
{
Fx.Assert(this.substate == Substate.Executing || this.substate == Substate.Canceling, "called from an unexpected state");
this.substate = Substate.Canceling;
}
void MarkExecuted()
{
this.substate = Substate.Executing;
}
internal void MarkAsComplete(BookmarkScopeManager bookmarkScopeManager, BookmarkManager bookmarkManager)
{
if (this.extendedData != null)
{
this.extendedData.PurgeBookmarks(bookmarkScopeManager, bookmarkManager, this);
if (this.extendedData.DataContext != null)
{
this.extendedData.DataContext.Dispose();
}
}
if (this.instanceMap != null)
{
this.instanceMap.RemoveEntry(this);
if (this.HasActivityReferences)
{
this.extendedData.PurgeActivityReferences(this.instanceMap);
}
}
if (this.Parent != null)
{
this.Parent.RemoveChild(this);
}
}
internal void Abort(ActivityExecutor executor, BookmarkManager bookmarkManager, Exception terminationReason, bool isTerminate)
{
// This is a gentle abort where we try to keep the runtime in a
// usable state.
AbortEnumerator abortEnumerator = new AbortEnumerator(this);
while (abortEnumerator.MoveNext())
{
ActivityInstance currentInstance = abortEnumerator.Current;
if (!currentInstance.HasNotExecuted)
{
currentInstance.Activity.InternalAbort(currentInstance, executor, terminationReason);
executor.DebugActivityCompleted(currentInstance);
}
if (currentInstance.PropertyManager != null)
{
currentInstance.PropertyManager.UnregisterProperties(currentInstance, currentInstance.Activity.MemberOf, true);
}
executor.TerminateSpecialExecutionBlocks(currentInstance, terminationReason);
executor.CancelPendingOperation(currentInstance);
executor.HandleRootCompletion(currentInstance);
currentInstance.MarkAsComplete(executor.RawBookmarkScopeManager, bookmarkManager);
currentInstance.state = ActivityInstanceState.Faulted;
currentInstance.FinalizeState(executor, false, !isTerminate);
}
}
internal void BaseCancel(NativeActivityContext context)
{
// Default cancelation logic starts here, but is also performed in
// UpdateState and through special completion work items
Fx.Assert(this.IsCancellationRequested, "This should be marked to true at this point.");
this.performingDefaultCancelation = true;
CancelChildren(context);
}
internal void CancelChildren(NativeActivityContext context)
{
if (this.HasChildren)
{
foreach (ActivityInstance child in this.GetChildren())
{
context.CancelChild(child);
}
}
}
internal void Cancel(ActivityExecutor executor, BookmarkManager bookmarkManager)
{
this.Activity.InternalCancel(this, executor, bookmarkManager);
}
internal void Execute(ActivityExecutor executor, BookmarkManager bookmarkManager)
{
if (this.initializationIncomplete)
{
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.InitializationIncomplete));
}
MarkExecuted();
this.Activity.InternalExecute(this, executor, bookmarkManager);
}
internal void AddChild(ActivityInstance item)
{
if (this.childList == null)
{
this.childList = new ChildList();
}
this.childList.Add(item);
this.childCache = null;
}
internal void RemoveChild(ActivityInstance item)
{
Fx.Assert(this.childList != null, "");
this.childList.Remove(item, true);
this.childCache = null;
}
// called by ActivityUtilities tree-walk
internal void AppendChildren(ActivityUtilities.TreeProcessingList nextInstanceList, ref Queue<IList<ActivityInstance>> instancesRemaining)
{
Fx.Assert(this.HasChildren, "AppendChildren is tuned to only be called when HasChildren is true");
this.childList.AppendChildren(nextInstanceList, ref instancesRemaining);
}
// called after deserialization of the workflow instance
internal void FixupInstance(ActivityInstance parent, ActivityInstanceMap instanceMap, ActivityExecutor executor)
{
if (this.IsCompleted)
{
// We hang onto the root instance even after is it complete. We skip the fixups
// for a completed root.
Fx.Assert(parent == null, "This should only happen to root instances.");
return;
}
if (this.Activity == null)
{
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.ActivityInstanceFixupFailed));
}
this.parent = parent;
this.instanceMap = instanceMap;
if (this.PropertyManager != null)
{
this.PropertyManager.OnDeserialized(this, parent, this.Activity.MemberOf, executor);
}
else if (this.parent != null)
{
// The current property manager is null here
this.PropertyManager = this.parent.PropertyManager;
}
else
{
this.PropertyManager = executor.RootPropertyManager;
}
if (!this.noSymbols)
{
this.environment.OnDeserialized(executor, this);
}
}
internal bool TryFixupChildren(ActivityInstanceMap instanceMap, ActivityExecutor executor)
{
if (!this.HasChildren)
{
return false;
}
this.childList.FixupList(this, instanceMap, executor);
return true;
}
internal void FillInstanceMap(ActivityInstanceMap instanceMap)
{
if (this.IsCompleted)
{
// We don't bother adding completed roots to the map
return;
}
Fx.Assert(this.instanceMap == null, "We should never call this unless the current map is null.");
Fx.Assert(this.Parent == null, "Can only generate a map from a root instance.");
this.instanceMap = instanceMap;
ActivityUtilities.ProcessActivityInstanceTree(this, null, new Func<ActivityInstance, ActivityExecutor, bool>(GenerateInstanceMapCallback));
}
bool GenerateInstanceMapCallback(ActivityInstance instance, ActivityExecutor executor)
{
this.instanceMap.AddEntry(instance);
instance.instanceMap = this.instanceMap;
if (instance.HasActivityReferences)
{
instance.extendedData.FillInstanceMap(instance.instanceMap);
}
return true;
}
internal bool Initialize(ActivityInstance parent, ActivityInstanceMap instanceMap, LocationEnvironment parentEnvironment, long instanceId, ActivityExecutor executor)
{
return this.Initialize(parent, instanceMap, parentEnvironment, instanceId, executor, 0);
}
internal bool Initialize(ActivityInstance parent, ActivityInstanceMap instanceMap, LocationEnvironment parentEnvironment, long instanceId, ActivityExecutor executor, int delegateParameterCount)
{
this.parent = parent;
this.instanceMap = instanceMap;
this.id = instanceId;
if (this.instanceMap != null)
{
this.instanceMap.AddEntry(this);
}
// propagate necessary information from our parent
if (this.parent != null)
{
if (this.parent.PropertyManager != null)
{
this.PropertyManager = this.parent.PropertyManager;
}
if (parentEnvironment == null)
{
parentEnvironment = this.parent.Environment;
}
}
int symbolCount = this.Activity.SymbolCount + delegateParameterCount;
if (symbolCount == 0)
{
if (parentEnvironment == null)
{
// We create an environment for a root activity that otherwise would not have one
// to simplify environment management.
this.environment = new LocationEnvironment(executor, this.Activity);
}
else
{
this.noSymbols = true;
this.environment = parentEnvironment;
}
// We don't set Initialized here since the tracking/tracing would be too early
return false;
}
else
{
this.environment = new LocationEnvironment(executor, this.Activity, parentEnvironment, symbolCount);
this.substate = Substate.ResolvingArguments;
return true;
}
}
internal void ResolveNewArgumentsDuringDynamicUpdate(ActivityExecutor executor, IList<int> dynamicUpdateArgumentIndexes)
{
Fx.Assert(!this.noSymbols, "Can only resolve arguments if we created an environment");
Fx.Assert(this.substate == Substate.Executing, "Dynamically added arguments are to be resolved only in Substate.Executing.");
if (this.Activity.SkipArgumentResolution)
{
return;
}
IList<RuntimeArgument> runtimeArguments = this.Activity.RuntimeArguments;
for (int i = 0; i < dynamicUpdateArgumentIndexes.Count; i++)
{
RuntimeArgument argument = runtimeArguments[dynamicUpdateArgumentIndexes[i]];
Fx.Assert(this.Environment.GetSpecificLocation(argument.Id) == null, "This is a newly added argument so the location should be null");
this.InternalTryPopulateArgumentValueOrScheduleExpression(argument, -1, executor, null, null, true);
}
}
private bool InternalTryPopulateArgumentValueOrScheduleExpression(RuntimeArgument argument, int nextArgumentIndex, ActivityExecutor executor, IDictionary<string, object> argumentValueOverrides, Location resultLocation, bool isDynamicUpdate)
{
object overrideValue = null;
if (argumentValueOverrides != null)
{
argumentValueOverrides.TryGetValue(argument.Name, out overrideValue);
}
if (argument.TryPopulateValue(this.environment, this, executor, overrideValue, resultLocation, isDynamicUpdate))
{
return true;
}
ResolveNextArgumentWorkItem workItem = null;
Location location = this.environment.GetSpecificLocation(argument.Id);
if (isDynamicUpdate)
{
//1. Check if this argument has a temporary location that needs to be collapsed
if (location.TemporaryResolutionEnvironment != null)
{
// 2. Add a workitem to collapse the temporary location
executor.ScheduleItem(new CollapseTemporaryResolutionLocationWorkItem(location, this));
}
}
else
{
//1. Check if there are more arguments to process
nextArgumentIndex = nextArgumentIndex + 1;
// 2. Add a workitem to resume argument resolution when
// work related to 3 below either completes or it hits an async point.
int totalArgumentCount = this.Activity.RuntimeArguments.Count;
if (nextArgumentIndex < totalArgumentCount)
{
workItem = executor.ResolveNextArgumentWorkItemPool.Acquire();
workItem.Initialize(this, nextArgumentIndex, argumentValueOverrides, resultLocation);
}
}
// 3. Schedule the argument expression.
executor.ScheduleExpression(argument.BoundArgument.Expression, this, this.Environment, location, workItem);
return false;
}
// return true if arguments were resolved synchronously
internal bool ResolveArguments(ActivityExecutor executor, IDictionary<string, object> argumentValueOverrides, Location resultLocation, int startIndex = 0)
{
Fx.Assert(!this.noSymbols, "Can only resolve arguments if we created an environment");
Fx.Assert(this.substate == Substate.ResolvingArguments, "Invalid sub-state machine");
bool completedSynchronously = true;
if (this.Activity.IsFastPath)
{
// We still need to resolve the result argument
Fx.Assert(argumentValueOverrides == null, "We shouldn't have any overrides.");
Fx.Assert(((ActivityWithResult)this.Activity).ResultRuntimeArgument != null, "We should have a result argument");
RuntimeArgument argument = ((ActivityWithResult)this.Activity).ResultRuntimeArgument;
if (!argument.TryPopulateValue(this.environment, this, executor, null, resultLocation, false))
{
completedSynchronously = false;
Location location = this.environment.GetSpecificLocation(argument.Id);
executor.ScheduleExpression(argument.BoundArgument.Expression, this, this.Environment, location, null);
}
}
else if (!this.Activity.SkipArgumentResolution)
{
IList<RuntimeArgument> runtimeArguments = this.Activity.RuntimeArguments;
int argumentCount = runtimeArguments.Count;
if (argumentCount > 0)
{
for (int i = startIndex; i < argumentCount; i++)
{
RuntimeArgument argument = runtimeArguments[i];
if (!this.InternalTryPopulateArgumentValueOrScheduleExpression(argument, i, executor, argumentValueOverrides, resultLocation, false))
{
completedSynchronously = false;
break;
}
}
}
}
if (completedSynchronously && startIndex == 0)
{
// We only move our state machine forward if this
// is the first call to ResolveArguments (startIndex
// == 0). Otherwise, a call to UpdateState will
// cause the substate switch (as well as a call to
// CollapseTemporaryResolutionLocations).
this.substate = Substate.ResolvingVariables;
}
return completedSynchronously;
}
internal void ResolveNewVariableDefaultsDuringDynamicUpdate(ActivityExecutor executor, IList<int> dynamicUpdateVariableIndexes, bool forImplementation)
{
Fx.Assert(!this.noSymbols, "Can only resolve variable default if we created an environment");
Fx.Assert(this.substate == Substate.Executing, "Dynamically added variable default expressions are to be resolved only in Substate.Executing.");
IList<Variable> runtimeVariables;
if (forImplementation)
{
runtimeVariables = this.Activity.ImplementationVariables;
}
else
{
runtimeVariables = this.Activity.RuntimeVariables;
}
for (int i = 0; i < dynamicUpdateVariableIndexes.Count; i++)
{
Variable newVariable = runtimeVariables[dynamicUpdateVariableIndexes[i]];
if (newVariable.Default != null)
{
EnqueueVariableDefault(executor, newVariable, null);
}
}
}
internal bool ResolveVariables(ActivityExecutor executor)
{
Fx.Assert(!this.noSymbols, "can only resolve variables if we created an environment");
Fx.Assert(this.substate == Substate.ResolvingVariables, "invalid sub-state machine");
this.substate = Substate.ResolvingVariables;
bool completedSynchronously = true;
IList<Variable> implementationVariables = this.Activity.ImplementationVariables;
IList<Variable> runtimeVariables = this.Activity.RuntimeVariables;
int implementationVariableCount = implementationVariables.Count;
int runtimeVariableCount = runtimeVariables.Count;
if (implementationVariableCount > 0 || runtimeVariableCount > 0)
{
for (int i = 0; i < implementationVariableCount; i++)
{
implementationVariables[i].DeclareLocation(executor, this);
}
for (int i = 0; i < runtimeVariableCount; i++)
{
runtimeVariables[i].DeclareLocation(executor, this);
}
for (int i = 0; i < implementationVariableCount; i++)
{
completedSynchronously &= ResolveVariable(implementationVariables[i], executor);
}
for (int i = 0; i < runtimeVariableCount; i++)
{
completedSynchronously &= ResolveVariable(runtimeVariables[i], executor);
}
}
return completedSynchronously;
}
// returns true if completed synchronously
bool ResolveVariable(Variable variable, ActivityExecutor executor)
{
bool completedSynchronously = true;
if (variable.Default != null)
{
Location variableLocation = this.Environment.GetSpecificLocation(variable.Id);
if (variable.Default.UseOldFastPath)
{
variable.PopulateDefault(executor, this, variableLocation);
}
else
{
EnqueueVariableDefault(executor, variable, variableLocation);
completedSynchronously = false;
}
}
return completedSynchronously;
}
void EnqueueVariableDefault(ActivityExecutor executor, Variable variable, Location variableLocation)
{
// Incomplete initialization detection logic relies on the fact that we
// don't specify a completion callback. If this changes we need to modify
// callers of SetInitializationIncomplete().
Fx.Assert(variable.Default != null, "If we've gone async we must have a default");
if (variableLocation == null)
{
variableLocation = this.environment.GetSpecificLocation(variable.Id);
}
variable.SetIsWaitingOnDefaultValue(variableLocation);
executor.ScheduleExpression(variable.Default, this, this.environment, variableLocation, null);
}
void ActivityInstanceMap.IActivityReference.Load(Activity activity, ActivityInstanceMap instanceMap)
{
if (activity.GetType().Name != this.OwnerName)
{
throw FxTrace.Exception.AsError(
new ValidationException(SR.ActivityTypeMismatch(activity.DisplayName, this.OwnerName)));
}
if (activity.ImplementationVersion != this.ImplementationVersion)
{
throw FxTrace.Exception.AsError(new VersionMismatchException(SR.ImplementationVersionMismatch(this.ImplementationVersion, activity.ImplementationVersion, activity)));
}
this.Activity = activity;
}
// Returns true if the activity completed
internal bool UpdateState(ActivityExecutor executor)
{
bool activityCompleted = false;
if (this.HasNotExecuted)
{
if (this.IsCancellationRequested) // need to cancel any in-flight resolutions and bail
{
if (this.HasChildren)
{
foreach (ActivityInstance child in this.GetChildren())
{
Fx.Assert(child.State == ActivityInstanceState.Executing, "should only have children if they're still executing");
executor.CancelActivity(child);
}
}
else
{
SetCanceled();
activityCompleted = true;
}
}
else if (!this.HasPendingWork)
{
bool scheduleBody = false;
if (this.substate == Substate.ResolvingArguments)
{
// if we've had asynchronous resolution of Locations (Out/InOut Arguments), resolve them now
this.Environment.CollapseTemporaryResolutionLocations();
this.substate = Substate.ResolvingVariables;
scheduleBody = ResolveVariables(executor);
}
else if (this.substate == Substate.ResolvingVariables)
{
scheduleBody = true;
}
if (scheduleBody)
{
executor.ScheduleBody(this, false, null, null);
}
}
Fx.Assert(this.HasPendingWork || activityCompleted, "should have scheduled work pending if we're not complete");
}
else if (!this.HasPendingWork)
{
if (!executor.IsCompletingTransaction(this))
{
activityCompleted = true;
if (this.substate == Substate.Canceling)
{
SetCanceled();
}
else
{
SetClosed();
}
}
}
else if (this.performingDefaultCancelation)
{
if (this.OnlyHasOutstandingBookmarks)
{
RemoveAllBookmarks(executor.RawBookmarkScopeManager, executor.RawBookmarkManager);
MarkCanceled();
Fx.Assert(!this.HasPendingWork, "Shouldn't have pending work here.");
SetCanceled();
activityCompleted = true;
}
}
return activityCompleted;
}
void TryCancelParent()
{
if (this.parent != null && this.parent.IsPerformingDefaultCancelation)
{
this.parent.MarkCanceled();
}
}
internal void SetInitializedSubstate(ActivityExecutor executor)
{
Fx.Assert(this.substate != Substate.Initialized, "SetInitializedSubstate called when substate is already Initialized.");
this.substate = Substate.Initialized;
if (executor.ShouldTrackActivityStateRecordsExecutingState)
{
if (executor.ShouldTrackActivity(this.Activity.DisplayName))
{
executor.AddTrackingRecord(new ActivityStateRecord(executor.WorkflowInstanceId, this, this.state));
}
}
if (TD.InArgumentBoundIsEnabled())
{
int runtimeArgumentsCount = this.Activity.RuntimeArguments.Count;
if (runtimeArgumentsCount > 0)
{
for (int i = 0; i < runtimeArgumentsCount; i++)
{
RuntimeArgument argument = this.Activity.RuntimeArguments[i];
if (ArgumentDirectionHelper.IsIn(argument.Direction))
{
Location location;
if (this.environment.TryGetLocation(argument.Id, this.Activity, out location))
{
string argumentValue = null;
if (location.Value == null)
{
argumentValue = "<Null>";
}
else
{
argumentValue = "'" + location.Value.ToString() + "'";
}
TD.InArgumentBound(argument.Name, this.Activity.GetType().ToString(), this.Activity.DisplayName, this.Id, argumentValue);
}
}
}
}
}
}
internal void FinalizeState(ActivityExecutor executor, bool faultActivity)
{
FinalizeState(executor, faultActivity, false);
}
internal void FinalizeState(ActivityExecutor executor, bool faultActivity, bool skipTracking)
{
if (faultActivity)
{
TryCancelParent();
// We can override previous completion states with this
this.state = ActivityInstanceState.Faulted;
}
Fx.Assert(this.state != ActivityInstanceState.Executing, "We must be in a completed state at this point.");
if (this.state == ActivityInstanceState.Closed)
{
if (executor.ShouldTrackActivityStateRecordsClosedState && !skipTracking)
{
if (executor.ShouldTrackActivity(this.Activity.DisplayName))
{
executor.AddTrackingRecord(new ActivityStateRecord(executor.WorkflowInstanceId, this, this.state));
}
}
}
else
{
if (executor.ShouldTrackActivityStateRecords && !skipTracking)
{
executor.AddTrackingRecord(new ActivityStateRecord(executor.WorkflowInstanceId, this, this.state));
}
}
if (TD.ActivityCompletedIsEnabled())
{
TD.ActivityCompleted(this.Activity.GetType().ToString(), this.Activity.DisplayName, this.Id, this.State.GetStateName());
}
}
void SetCanceled()
{
Fx.Assert(!this.IsCompleted, "Should not be completed if we are changing the state.");
TryCancelParent();
this.state = ActivityInstanceState.Canceled;
}
void SetClosed()
{
Fx.Assert(!this.IsCompleted, "Should not be completed if we are changing the state.");
this.state = ActivityInstanceState.Closed;
}
static void UpdateLocationEnvironmentHierarchy(LocationEnvironment oldParentEnvironment, LocationEnvironment newEnvironment, ActivityInstance currentInstance)
{
Func<ActivityInstance, ActivityExecutor, bool> processInstanceCallback = delegate(ActivityInstance instance, ActivityExecutor executor)
{
if (instance == currentInstance)
{
return true;
}
if (instance.IsEnvironmentOwner)
{
if (instance.environment.Parent == oldParentEnvironment)
{
// overwrite its parent with newEnvironment
instance.environment.Parent = newEnvironment;
}
// We do not need to process children instances beyond this point.
return false;
}
if (instance.environment == oldParentEnvironment)
{
// this instance now points to newEnvironment
instance.environment = newEnvironment;
}
return true;
};
ActivityUtilities.ProcessActivityInstanceTree(currentInstance, null, processInstanceCallback);
}
void ActivityInstanceMap.IActivityReferenceWithEnvironment.UpdateEnvironment(EnvironmentUpdateMap map, Activity activity)
{
Fx.Assert(this.substate != Substate.ResolvingVariables, "We must have already performed the same validations in advance.");
Fx.Assert(this.substate != Substate.ResolvingArguments, "We must have already performed the same validations in advance.");
if (this.noSymbols)
{
// create a new LocationReference and this ActivityInstance becomes the owner of the created environment.
LocationEnvironment oldParentEnvironment = this.environment;
Fx.Assert(oldParentEnvironment != null, "environment must never be null.");
this.environment = new LocationEnvironment(oldParentEnvironment, map.NewArgumentCount + map.NewVariableCount + map.NewPrivateVariableCount + map.RuntimeDelegateArgumentCount);
this.noSymbols = false;
// traverse the activity instance chain.
// Update all its non-environment-owning decedent instances to point to the newly created enviroment,
// and, update all its environment-owning decendent instances to have their environment's parent to point to the newly created environment.
UpdateLocationEnvironmentHierarchy(oldParentEnvironment, this.environment, this);
}
this.Environment.Update(map, activity);
}
internal enum Substate : byte
{
Executing = 0, // choose the most common persist-time state for the default
PreExecuting = 0x80, // used for all states prior to "core execution"
Created = 1 | Substate.PreExecuting,
ResolvingArguments = 2 | Substate.PreExecuting,
// ResolvedArguments = 2,
ResolvingVariables = 3 | Substate.PreExecuting,
// ResolvedVariables = 3,
Initialized = 4 | Substate.PreExecuting,
Canceling = 5,
}
// data necessary to support non-mainline usage of instances (i.e. creating bookmarks, using transactions)
[DataContract]
internal class ExtendedData
{
BookmarkList bookmarks;
ActivityReferenceList activityReferences;
int blockingBookmarkCount;
public ExtendedData()
{
}
public int BlockingBookmarkCount
{
get
{
return blockingBookmarkCount;
}
private set
{
blockingBookmarkCount = value;
}
}
[DataMember(Name = XD.ActivityInstance.WaitingForTransactionContext, EmitDefaultValue = false)]
public bool WaitingForTransactionContext
{
get;
set;
}
[DataMember(Name = XD.ActivityInstance.FaultBookmark, EmitDefaultValue = false)]
public FaultBookmark FaultBookmark
{
get;
set;
}
public WorkflowDataContext DataContext
{
get;
set;
}
[DataMember(Name = XD.ActivityInstance.BlockingBookmarkCount, EmitDefaultValue = false)]
internal int SerializedBlockingBookmarkCount
{
get { return this.BlockingBookmarkCount; }
set { this.BlockingBookmarkCount = value; }
}
[DataMember(Name = XD.ActivityInstance.Bookmarks, EmitDefaultValue = false)]
[SuppressMessage(FxCop.Category.Performance, FxCop.Rule.AvoidUncalledPrivateCode, Justification = "Called from Serialization")]
internal BookmarkList Bookmarks
{
get
{
if (this.bookmarks == null || this.bookmarks.Count == 0)
{
return null;
}
else
{
return this.bookmarks;
}
}
set
{
Fx.Assert(value != null, "We don't emit the default value so this should never be null.");
this.bookmarks = value;
}
}
[DataMember(Name = XD.ActivityInstance.ActivityReferences, EmitDefaultValue = false)]
[SuppressMessage(FxCop.Category.Performance, FxCop.Rule.AvoidUncalledPrivateCode, Justification = "Called from Serialization")]
internal ActivityReferenceList ActivityReferences
{
get
{
if (this.activityReferences == null || this.activityReferences.Count == 0)
{
return null;
}
else
{
return this.activityReferences;
}
}
set
{
Fx.Assert(value != null && value.Count > 0, "We shouldn't emit the default value or empty lists");
this.activityReferences = value;
}
}
public bool HasActivityReferences
{
get
{
return this.activityReferences != null && this.activityReferences.Count > 0;
}
}
public void AddBookmark(Bookmark bookmark, bool affectsBusyCount)
{
if (this.bookmarks == null)
{
this.bookmarks = new BookmarkList();
}
if (affectsBusyCount)
{
this.BlockingBookmarkCount = this.BlockingBookmarkCount + 1;
}
this.bookmarks.Add(bookmark);
}
public void RemoveBookmark(Bookmark bookmark, bool affectsBusyCount)
{
Fx.Assert(this.bookmarks != null, "The bookmark list should have been initialized if we are trying to remove one.");
if (affectsBusyCount)
{
Fx.Assert(this.BlockingBookmarkCount > 0, "We should never decrement below zero.");
this.BlockingBookmarkCount = this.BlockingBookmarkCount - 1;
}
this.bookmarks.Remove(bookmark);
}
public void PurgeBookmarks(BookmarkScopeManager bookmarkScopeManager, BookmarkManager bookmarkManager, ActivityInstance owningInstance)
{
if (this.bookmarks != null)
{
if (this.bookmarks.Count > 0)
{
Bookmark singleBookmark;
IList<Bookmark> multipleBookmarks;
this.bookmarks.TransferBookmarks(out singleBookmark, out multipleBookmarks);
this.bookmarks = null;
if (bookmarkScopeManager != null)
{
bookmarkScopeManager.PurgeBookmarks(bookmarkManager, singleBookmark, multipleBookmarks);
}
else
{
bookmarkManager.PurgeBookmarks(singleBookmark, multipleBookmarks);
}
// Clean up the busy count
owningInstance.DecrementBusyCount(this.BlockingBookmarkCount);
this.BlockingBookmarkCount = 0;
}
}
}
public void AddActivityReference(ActivityInstanceReference reference)
{
if (this.activityReferences == null)
{
this.activityReferences = new ActivityReferenceList();
}
this.activityReferences.Add(reference);
}
public void FillInstanceMap(ActivityInstanceMap instanceMap)
{
Fx.Assert(this.HasActivityReferences, "Must have references to have called this.");
this.activityReferences.FillInstanceMap(instanceMap);
}
public void PurgeActivityReferences(ActivityInstanceMap instanceMap)
{
Fx.Assert(this.HasActivityReferences, "Must have references to have called this.");
this.activityReferences.PurgeActivityReferences(instanceMap);
}
[DataContract]
internal class ActivityReferenceList : HybridCollection<ActivityInstanceReference>
{
public ActivityReferenceList()
: base()
{
}
public void FillInstanceMap(ActivityInstanceMap instanceMap)
{
Fx.Assert(this.Count > 0, "Should only call this when we have items");
if (this.SingleItem != null)
{
instanceMap.AddEntry(this.SingleItem);
}
else
{
for (int i = 0; i < this.MultipleItems.Count; i++)
{
ActivityInstanceReference reference = this.MultipleItems[i];
instanceMap.AddEntry(reference);
}
}
}
public void PurgeActivityReferences(ActivityInstanceMap instanceMap)
{
Fx.Assert(this.Count > 0, "Should only call this when we have items");
if (this.SingleItem != null)
{
instanceMap.RemoveEntry(this.SingleItem);
}
else
{
for (int i = 0; i < this.MultipleItems.Count; i++)
{
instanceMap.RemoveEntry(this.MultipleItems[i]);
}
}
}
}
}
[DataContract]
internal class ChildList : HybridCollection<ActivityInstance>
{
static ReadOnlyCollection<ActivityInstance> emptyChildren;
public ChildList()
: base()
{
}
public static ReadOnlyCollection<ActivityInstance> Empty
{
get
{
if (emptyChildren == null)
{
emptyChildren = new ReadOnlyCollection<ActivityInstance>(new ActivityInstance[0]);
}
return emptyChildren;
}
}
public void AppendChildren(ActivityUtilities.TreeProcessingList nextInstanceList, ref Queue<IList<ActivityInstance>> instancesRemaining)
{
// This is only called if there is at least one item in the list.
if (base.SingleItem != null)
{
nextInstanceList.Add(base.SingleItem);
}
else if (nextInstanceList.Count == 0)
{
nextInstanceList.Set(base.MultipleItems);
}
else
{
// Next instance list already has some stuff and we have multiple
// items. Let's enqueue them for later processing.
if (instancesRemaining == null)
{
instancesRemaining = new Queue<IList<ActivityInstance>>();
}
instancesRemaining.Enqueue(base.MultipleItems);
}
}
public void FixupList(ActivityInstance parent, ActivityInstanceMap instanceMap, ActivityExecutor executor)
{
if (base.SingleItem != null)
{
base.SingleItem.FixupInstance(parent, instanceMap, executor);
}
else
{
for (int i = 0; i < base.MultipleItems.Count; i++)
{
base.MultipleItems[i].FixupInstance(parent, instanceMap, executor);
}
}
}
}
// Does a depth first walk and uses some knowledge of
// the abort process to determine which child to visit next
class AbortEnumerator : IEnumerator<ActivityInstance>
{
ActivityInstance root;
ActivityInstance current;
bool initialized;
public AbortEnumerator(ActivityInstance root)
{
this.root = root;
}
public ActivityInstance Current
{
get
{
return this.current;
}
}
object IEnumerator.Current
{
get
{
return this.Current;
}
}
public bool MoveNext()
{
if (!this.initialized)
{
this.current = root;
// We start by diving down the tree along the
// "first child" path
while (this.current.HasChildren)
{
this.current = this.current.GetChildren()[0];
}
this.initialized = true;
return true;
}
else
{
if (this.current == this.root)
{
// We're done if we returned all the way to the root last time
return false;
}
else
{
Fx.Assert(!this.current.Parent.GetChildren().Contains(this.current), "We should always have removed the current one from the parent's list by now.");
this.current = this.current.Parent;
// Dive down the tree of remaining first children
while (this.current.HasChildren)
{
this.current = this.current.GetChildren()[0];
}
return true;
}
}
}
public void Reset()
{
this.current = null;
this.initialized = false;
}
public void Dispose()
{
// no op
}
}
}
}
|