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