e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1161 lines
39 KiB
C#
1161 lines
39 KiB
C#
//-----------------------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace System.Activities.Hosting
|
|
{
|
|
using System;
|
|
using System.Text;
|
|
using System.Activities.DynamicUpdate;
|
|
using System.Activities.Runtime;
|
|
using System.Activities.Tracking;
|
|
using System.Activities.Validation;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.Runtime;
|
|
using System.Runtime.DurableInstancing;
|
|
using System.Threading;
|
|
using System.Xml.Linq;
|
|
|
|
[Fx.Tag.XamlVisible(false)]
|
|
public abstract class WorkflowInstance
|
|
{
|
|
static readonly IDictionary<string, LocationInfo> EmptyMappedVariablesDictionary = new ReadOnlyDictionaryInternal<string, LocationInfo>(new Dictionary<string, LocationInfo>(0));
|
|
|
|
const int True = 1;
|
|
const int False = 0;
|
|
|
|
WorkflowInstanceControl controller;
|
|
TrackingProvider trackingProvider;
|
|
SynchronizationContext syncContext;
|
|
LocationReferenceEnvironment hostEnvironment;
|
|
ActivityExecutor executor;
|
|
int isPerformingOperation;
|
|
bool isInitialized;
|
|
WorkflowInstanceExtensionCollection extensions;
|
|
|
|
// Tracking for one-time actions per in-memory instance
|
|
bool hasTrackedResumed;
|
|
bool hasTrackedCompletion;
|
|
|
|
bool isAborted;
|
|
Exception abortedException;
|
|
|
|
#if DEBUG
|
|
StackTrace abortStack;
|
|
#endif
|
|
|
|
protected WorkflowInstance(Activity workflowDefinition)
|
|
: this(workflowDefinition, null)
|
|
{
|
|
}
|
|
|
|
protected WorkflowInstance(Activity workflowDefinition, WorkflowIdentity definitionIdentity)
|
|
{
|
|
if (workflowDefinition == null)
|
|
{
|
|
throw FxTrace.Exception.ArgumentNull("workflowDefinition");
|
|
}
|
|
|
|
this.WorkflowDefinition = workflowDefinition;
|
|
this.DefinitionIdentity = definitionIdentity;
|
|
}
|
|
|
|
public abstract Guid Id
|
|
{
|
|
get;
|
|
}
|
|
|
|
internal bool HasTrackingParticipant
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
internal bool HasTrackedStarted
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
internal bool HasPersistenceModule
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public SynchronizationContext SynchronizationContext
|
|
{
|
|
get
|
|
{
|
|
return this.syncContext;
|
|
}
|
|
set
|
|
{
|
|
ThrowIfReadOnly();
|
|
this.syncContext = value;
|
|
}
|
|
}
|
|
|
|
public LocationReferenceEnvironment HostEnvironment
|
|
{
|
|
get
|
|
{
|
|
return this.hostEnvironment;
|
|
}
|
|
set
|
|
{
|
|
ThrowIfReadOnly();
|
|
this.hostEnvironment = value;
|
|
}
|
|
}
|
|
|
|
public Activity WorkflowDefinition
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public WorkflowIdentity DefinitionIdentity
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
protected bool IsReadOnly
|
|
{
|
|
get
|
|
{
|
|
return this.isInitialized;
|
|
}
|
|
}
|
|
|
|
protected internal abstract bool SupportsInstanceKeys
|
|
{
|
|
get;
|
|
}
|
|
|
|
// this is going away
|
|
internal TrackingProvider TrackingProvider
|
|
{
|
|
get
|
|
{
|
|
Fx.Assert(HasTrackingParticipant, "we should only be called if we have a tracking participant");
|
|
return this.trackingProvider;
|
|
}
|
|
}
|
|
|
|
protected WorkflowInstanceControl Controller
|
|
{
|
|
get
|
|
{
|
|
if (!this.isInitialized)
|
|
{
|
|
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.ControllerInvalidBeforeInitialize));
|
|
}
|
|
|
|
return this.controller;
|
|
}
|
|
}
|
|
|
|
// host-facing access to our cascading ExtensionManager resolution
|
|
protected internal T GetExtension<T>() where T : class
|
|
{
|
|
if (this.extensions != null)
|
|
{
|
|
return this.extensions.Find<T>();
|
|
}
|
|
else
|
|
{
|
|
return default(T);
|
|
}
|
|
}
|
|
|
|
protected internal IEnumerable<T> GetExtensions<T>() where T : class
|
|
{
|
|
if (this.extensions != null)
|
|
{
|
|
return this.extensions.FindAll<T>();
|
|
}
|
|
else
|
|
{
|
|
return new T[0];
|
|
}
|
|
}
|
|
|
|
// locks down the given extensions manager and runs cache metadata on the workflow definition
|
|
protected void RegisterExtensionManager(WorkflowInstanceExtensionManager extensionManager)
|
|
{
|
|
ValidateWorkflow(extensionManager);
|
|
this.extensions = WorkflowInstanceExtensionManager.CreateInstanceExtensions(this.WorkflowDefinition, extensionManager);
|
|
if (this.extensions != null)
|
|
{
|
|
this.HasPersistenceModule = this.extensions.HasPersistenceModule;
|
|
}
|
|
}
|
|
|
|
// dispose the extensions that implement IDisposable
|
|
protected void DisposeExtensions()
|
|
{
|
|
if (this.extensions != null)
|
|
{
|
|
this.extensions.Dispose();
|
|
this.extensions = null;
|
|
}
|
|
}
|
|
|
|
protected static IList<ActivityBlockingUpdate> GetActivitiesBlockingUpdate(object deserializedRuntimeState, DynamicUpdateMap updateMap)
|
|
{
|
|
ActivityExecutor executor = deserializedRuntimeState as ActivityExecutor;
|
|
if (executor == null)
|
|
{
|
|
throw FxTrace.Exception.Argument("deserializedRuntimeState", SR.InvalidRuntimeState);
|
|
}
|
|
if (updateMap == null)
|
|
{
|
|
throw FxTrace.Exception.ArgumentNull("updateMap");
|
|
}
|
|
|
|
DynamicUpdateMap rootMap = updateMap;
|
|
if (updateMap.IsForImplementation)
|
|
{
|
|
rootMap = updateMap.AsRootMap();
|
|
}
|
|
IList<ActivityBlockingUpdate> result = executor.GetActivitiesBlockingUpdate(rootMap);
|
|
if (result == null)
|
|
{
|
|
result = new List<ActivityBlockingUpdate>();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// used for Create scenarios where you are providing root information
|
|
protected void Initialize(IDictionary<string, object> workflowArgumentValues, IList<Handle> workflowExecutionProperties)
|
|
{
|
|
ThrowIfAborted();
|
|
ThrowIfReadOnly();
|
|
this.executor = new ActivityExecutor(this);
|
|
|
|
EnsureDefinitionReady();
|
|
// workflowArgumentValues signals whether we are a new or loaded instance, so we can't pass in null.
|
|
// workflowExecutionProperties is allowed to be null
|
|
InitializeCore(workflowArgumentValues ?? ActivityUtilities.EmptyParameters, workflowExecutionProperties);
|
|
}
|
|
|
|
// used for Load scenarios where you are rehydrating a WorkflowInstance
|
|
protected void Initialize(object deserializedRuntimeState)
|
|
{
|
|
Initialize(deserializedRuntimeState, null);
|
|
}
|
|
|
|
protected void Initialize(object deserializedRuntimeState, DynamicUpdateMap updateMap)
|
|
{
|
|
ThrowIfAborted();
|
|
ThrowIfReadOnly();
|
|
this.executor = deserializedRuntimeState as ActivityExecutor;
|
|
|
|
if (this.executor == null)
|
|
{
|
|
throw FxTrace.Exception.Argument("deserializedRuntimeState", SR.InvalidRuntimeState);
|
|
}
|
|
this.executor.ThrowIfNonSerializable();
|
|
|
|
EnsureDefinitionReady();
|
|
|
|
WorkflowIdentity originalDefinitionIdentity = this.executor.WorkflowIdentity;
|
|
bool success = false;
|
|
Collection<ActivityBlockingUpdate> updateErrors = null;
|
|
try
|
|
{
|
|
if (updateMap != null)
|
|
{
|
|
// check if map is for implementaiton,
|
|
if (updateMap.IsForImplementation)
|
|
{
|
|
// if so, the definition root must be an activity
|
|
// with no public/imported children and no public/imported delegates.
|
|
if (DynamicUpdateMap.CanUseImplementationMapAsRoot(this.WorkflowDefinition))
|
|
{
|
|
updateMap = updateMap.AsRootMap();
|
|
}
|
|
else
|
|
{
|
|
throw FxTrace.Exception.AsError(new InstanceUpdateException(SR.InvalidImplementationAsWorkflowRoot));
|
|
}
|
|
}
|
|
|
|
updateMap.ThrowIfInvalid(this.WorkflowDefinition);
|
|
|
|
this.executor.WorkflowIdentity = this.DefinitionIdentity;
|
|
|
|
this.executor.UpdateInstancePhase1(updateMap, this.WorkflowDefinition, ref updateErrors);
|
|
ThrowIfDynamicUpdateErrorExists(updateErrors);
|
|
}
|
|
|
|
InitializeCore(null, null);
|
|
|
|
if (updateMap != null)
|
|
{
|
|
this.executor.UpdateInstancePhase2(updateMap, ref updateErrors);
|
|
ThrowIfDynamicUpdateErrorExists(updateErrors);
|
|
// Track that dynamic update is successful
|
|
if (this.Controller.TrackingEnabled)
|
|
{
|
|
this.Controller.Track(new WorkflowInstanceUpdatedRecord(this.Id, this.WorkflowDefinition.DisplayName, originalDefinitionIdentity, this.executor.WorkflowIdentity));
|
|
}
|
|
}
|
|
|
|
success = true;
|
|
}
|
|
catch (InstanceUpdateException updateException)
|
|
{
|
|
// Can't track through the controller because initialization failed
|
|
if (this.HasTrackingParticipant && this.TrackingProvider.ShouldTrackWorkflowInstanceRecords)
|
|
{
|
|
IList<ActivityBlockingUpdate> blockingActivities = updateException.BlockingActivities;
|
|
if (blockingActivities.Count == 0)
|
|
{
|
|
blockingActivities = new List<ActivityBlockingUpdate>
|
|
{
|
|
new ActivityBlockingUpdate(this.WorkflowDefinition, this.WorkflowDefinition.Id, updateException.Message)
|
|
}.AsReadOnly();
|
|
}
|
|
this.TrackingProvider.AddRecord(new WorkflowInstanceUpdatedRecord(this.Id, this.WorkflowDefinition.DisplayName, originalDefinitionIdentity, this.DefinitionIdentity, blockingActivities));
|
|
}
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
if (updateMap != null && !success)
|
|
{
|
|
executor.MakeNonSerializable();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ThrowIfDynamicUpdateErrorExists(Collection<ActivityBlockingUpdate> updateErrors)
|
|
{
|
|
if (updateErrors != null && updateErrors.Count > 0)
|
|
{
|
|
// update error found
|
|
// exit early
|
|
|
|
throw FxTrace.Exception.AsError(new InstanceUpdateException(updateErrors));
|
|
}
|
|
}
|
|
|
|
void ValidateWorkflow(WorkflowInstanceExtensionManager extensionManager)
|
|
{
|
|
if (!WorkflowDefinition.IsRuntimeReady)
|
|
{
|
|
LocationReferenceEnvironment localEnvironment = this.hostEnvironment;
|
|
if (localEnvironment == null)
|
|
{
|
|
LocationReferenceEnvironment parentEnvironment = null;
|
|
if (extensionManager != null && extensionManager.SymbolResolver != null)
|
|
{
|
|
parentEnvironment = extensionManager.SymbolResolver.AsLocationReferenceEnvironment();
|
|
}
|
|
localEnvironment = new ActivityLocationReferenceEnvironment(parentEnvironment);
|
|
}
|
|
IList<ValidationError> validationErrors = null;
|
|
ActivityUtilities.CacheRootMetadata(WorkflowDefinition, localEnvironment, ProcessActivityTreeOptions.FullCachingOptions, null, ref validationErrors);
|
|
ActivityValidationServices.ThrowIfViolationsExist(validationErrors);
|
|
}
|
|
}
|
|
|
|
void EnsureDefinitionReady()
|
|
{
|
|
if (this.extensions != null)
|
|
{
|
|
this.extensions.Initialize();
|
|
if (this.extensions.HasTrackingParticipant)
|
|
{
|
|
this.HasTrackingParticipant = true;
|
|
if (this.trackingProvider == null)
|
|
{
|
|
this.trackingProvider = new TrackingProvider(this.WorkflowDefinition);
|
|
}
|
|
else
|
|
{
|
|
// TrackingProvider could be non-null if an earlier initialization attempt failed.
|
|
// This happens when WorkflowApplication calls Abort after a load failure. In this
|
|
// case we want to preserve any pending tracking records (e.g. DU failure).
|
|
this.trackingProvider.ClearParticipants();
|
|
}
|
|
foreach (TrackingParticipant trackingParticipant in GetExtensions<TrackingParticipant>())
|
|
{
|
|
this.trackingProvider.AddParticipant(trackingParticipant);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// need to ensure the workflow has been validated since the host isn't using extensions (and so didn't register anything)
|
|
ValidateWorkflow(null);
|
|
}
|
|
}
|
|
|
|
void InitializeCore(IDictionary<string, object> workflowArgumentValues, IList<Handle> workflowExecutionProperties)
|
|
{
|
|
Fx.Assert(this.WorkflowDefinition.IsRuntimeReady, "EnsureDefinitionReady should have been called");
|
|
Fx.Assert(this.executor != null, "at this point, we better have an executor");
|
|
|
|
// Do Argument validation for root activities
|
|
WorkflowDefinition.HasBeenAssociatedWithAnInstance = true;
|
|
|
|
if (workflowArgumentValues != null)
|
|
{
|
|
IDictionary<string, object> actualInputs = workflowArgumentValues;
|
|
|
|
if (object.ReferenceEquals(actualInputs, ActivityUtilities.EmptyParameters))
|
|
{
|
|
actualInputs = null;
|
|
}
|
|
|
|
if (this.WorkflowDefinition.RuntimeArguments.Count > 0 || (actualInputs != null && actualInputs.Count > 0))
|
|
{
|
|
ActivityValidationServices.ValidateRootInputs(this.WorkflowDefinition, actualInputs);
|
|
}
|
|
|
|
this.executor.ScheduleRootActivity(this.WorkflowDefinition, actualInputs, workflowExecutionProperties);
|
|
}
|
|
else
|
|
{
|
|
this.executor.OnDeserialized(this.WorkflowDefinition, this);
|
|
}
|
|
|
|
this.executor.Open(this.SynchronizationContext);
|
|
this.controller = new WorkflowInstanceControl(this, this.executor);
|
|
this.isInitialized = true;
|
|
|
|
if (this.extensions != null && this.extensions.HasWorkflowInstanceExtensions)
|
|
{
|
|
WorkflowInstanceProxy proxy = new WorkflowInstanceProxy(this);
|
|
|
|
for (int i = 0; i < this.extensions.WorkflowInstanceExtensions.Count; i++)
|
|
{
|
|
IWorkflowInstanceExtension extension = this.extensions.WorkflowInstanceExtensions[i];
|
|
extension.SetInstance(proxy);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void ThrowIfReadOnly()
|
|
{
|
|
if (this.isInitialized)
|
|
{
|
|
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.WorkflowInstanceIsReadOnly(this.Id)));
|
|
}
|
|
}
|
|
|
|
protected internal abstract IAsyncResult OnBeginResumeBookmark(Bookmark bookmark, object value, TimeSpan timeout, AsyncCallback callback, object state);
|
|
protected internal abstract BookmarkResumptionResult OnEndResumeBookmark(IAsyncResult result);
|
|
|
|
protected internal abstract IAsyncResult OnBeginPersist(AsyncCallback callback, object state);
|
|
protected internal abstract void OnEndPersist(IAsyncResult result);
|
|
|
|
protected internal abstract void OnDisassociateKeys(ICollection<InstanceKey> keys);
|
|
|
|
protected internal abstract IAsyncResult OnBeginAssociateKeys(ICollection<InstanceKey> keys, AsyncCallback callback, object state);
|
|
protected internal abstract void OnEndAssociateKeys(IAsyncResult result);
|
|
|
|
internal IAsyncResult BeginFlushTrackingRecordsInternal(AsyncCallback callback, object state)
|
|
{
|
|
return OnBeginFlushTrackingRecords(callback, state);
|
|
}
|
|
|
|
internal void EndFlushTrackingRecordsInternal(IAsyncResult result)
|
|
{
|
|
OnEndFlushTrackingRecords(result);
|
|
}
|
|
|
|
protected void FlushTrackingRecords(TimeSpan timeout)
|
|
{
|
|
if (this.HasTrackingParticipant)
|
|
{
|
|
this.TrackingProvider.FlushPendingRecords(timeout);
|
|
}
|
|
}
|
|
|
|
protected IAsyncResult BeginFlushTrackingRecords(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
if (this.HasTrackingParticipant)
|
|
{
|
|
return this.TrackingProvider.BeginFlushPendingRecords(timeout, callback, state);
|
|
}
|
|
else
|
|
{
|
|
return new CompletedAsyncResult(callback, state);
|
|
}
|
|
}
|
|
|
|
protected void EndFlushTrackingRecords(IAsyncResult result)
|
|
{
|
|
if (this.HasTrackingParticipant)
|
|
{
|
|
this.TrackingProvider.EndFlushPendingRecords(result);
|
|
}
|
|
else
|
|
{
|
|
CompletedAsyncResult.End(result);
|
|
}
|
|
}
|
|
|
|
protected virtual IAsyncResult OnBeginFlushTrackingRecords(AsyncCallback callback, object state)
|
|
{
|
|
return this.Controller.BeginFlushTrackingRecords(ActivityDefaults.TrackingTimeout, callback, state);
|
|
}
|
|
|
|
protected virtual void OnEndFlushTrackingRecords(IAsyncResult result)
|
|
{
|
|
this.Controller.EndFlushTrackingRecords(result);
|
|
}
|
|
|
|
internal void NotifyPaused()
|
|
{
|
|
if (this.executor.State != ActivityInstanceState.Executing)
|
|
{
|
|
TrackCompletion();
|
|
}
|
|
|
|
OnNotifyPaused();
|
|
}
|
|
|
|
protected abstract void OnNotifyPaused();
|
|
|
|
internal void NotifyUnhandledException(Exception exception, Activity source, string sourceInstanceId)
|
|
{
|
|
if (this.controller.TrackingEnabled)
|
|
{
|
|
ActivityInfo faultSourceInfo = new ActivityInfo(source.DisplayName, source.Id, sourceInstanceId, source.GetType().FullName);
|
|
this.controller.Track(new WorkflowInstanceUnhandledExceptionRecord(this.Id, this.WorkflowDefinition.DisplayName, faultSourceInfo, exception, this.DefinitionIdentity));
|
|
}
|
|
|
|
OnNotifyUnhandledException(exception, source, sourceInstanceId);
|
|
}
|
|
|
|
protected abstract void OnNotifyUnhandledException(Exception exception, Activity source, string sourceInstanceId);
|
|
|
|
protected internal abstract void OnRequestAbort(Exception reason);
|
|
|
|
internal void OnDeserialized(bool hasTrackedStarted)
|
|
{
|
|
this.HasTrackedStarted = hasTrackedStarted;
|
|
}
|
|
|
|
void StartOperation(ref bool resetRequired)
|
|
{
|
|
StartReadOnlyOperation(ref resetRequired);
|
|
|
|
// isRunning can only flip to true by an operation and therefore
|
|
// we don't have to worry about this changing under us
|
|
if (this.executor.IsRunning)
|
|
{
|
|
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.RuntimeRunning));
|
|
}
|
|
}
|
|
|
|
void StartReadOnlyOperation(ref bool resetRequired)
|
|
{
|
|
bool wasPerformingOperation = false;
|
|
try
|
|
{
|
|
}
|
|
finally
|
|
{
|
|
wasPerformingOperation = Interlocked.CompareExchange(ref this.isPerformingOperation, True, False) == True;
|
|
|
|
if (!wasPerformingOperation)
|
|
{
|
|
resetRequired = true;
|
|
}
|
|
}
|
|
|
|
if (wasPerformingOperation)
|
|
{
|
|
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.RuntimeOperationInProgress));
|
|
}
|
|
}
|
|
|
|
void FinishOperation(ref bool resetRequired)
|
|
{
|
|
if (resetRequired)
|
|
{
|
|
this.isPerformingOperation = False;
|
|
}
|
|
}
|
|
|
|
internal void Abort(Exception reason)
|
|
{
|
|
if (!this.isAborted)
|
|
{
|
|
this.isAborted = true;
|
|
if (reason != null)
|
|
{
|
|
this.abortedException = reason;
|
|
}
|
|
|
|
if (this.extensions != null)
|
|
{
|
|
this.extensions.Cancel();
|
|
}
|
|
|
|
if (this.controller.TrackingEnabled)
|
|
{
|
|
// During abort we only track this one record
|
|
if (reason != null)
|
|
{
|
|
string message = reason.Message;
|
|
if (reason.InnerException != null)
|
|
{
|
|
message = SR.WorkflowAbortedReason(reason.Message, reason.InnerException.Message);
|
|
}
|
|
this.controller.Track(new WorkflowInstanceAbortedRecord(this.Id, this.WorkflowDefinition.DisplayName, message, this.DefinitionIdentity));
|
|
}
|
|
}
|
|
#if DEBUG
|
|
if (!Fx.FastDebug)
|
|
{
|
|
if (reason != null)
|
|
{
|
|
reason.ToString();
|
|
}
|
|
this.abortStack = new StackTrace();
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void ValidatePrepareForSerialization()
|
|
{
|
|
ThrowIfAborted();
|
|
if (!this.Controller.IsPersistable)
|
|
{
|
|
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.PrepareForSerializationRequiresPersistability));
|
|
}
|
|
}
|
|
|
|
void ValidateScheduleResumeBookmark()
|
|
{
|
|
ThrowIfAborted();
|
|
ThrowIfNotIdle();
|
|
}
|
|
|
|
void ValidateGetBookmarks()
|
|
{
|
|
ThrowIfAborted();
|
|
}
|
|
|
|
void ValidateGetMappedVariables()
|
|
{
|
|
ThrowIfAborted();
|
|
}
|
|
|
|
void ValidatePauseWhenPersistable()
|
|
{
|
|
ThrowIfAborted();
|
|
if (this.Controller.IsPersistable)
|
|
{
|
|
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.PauseWhenPersistableInvalidIfPersistable));
|
|
}
|
|
}
|
|
|
|
void Terminate(Exception reason)
|
|
{
|
|
// validate we're in an ok state
|
|
ThrowIfAborted();
|
|
|
|
// terminate the runtime
|
|
this.executor.Terminate(reason);
|
|
|
|
// and track if necessary
|
|
TrackCompletion();
|
|
|
|
}
|
|
|
|
void TrackCompletion()
|
|
{
|
|
if (this.controller.TrackingEnabled && !this.hasTrackedCompletion)
|
|
{
|
|
ActivityInstanceState completionState = this.executor.State;
|
|
|
|
if (completionState == ActivityInstanceState.Faulted)
|
|
{
|
|
Fx.Assert(this.executor.TerminationException != null, "must have a termination exception if we're faulted");
|
|
this.controller.Track(new WorkflowInstanceTerminatedRecord(this.Id, this.WorkflowDefinition.DisplayName, this.executor.TerminationException.Message, this.DefinitionIdentity));
|
|
}
|
|
else if (completionState == ActivityInstanceState.Closed)
|
|
{
|
|
this.controller.Track(new WorkflowInstanceRecord(this.Id, this.WorkflowDefinition.DisplayName, WorkflowInstanceStates.Completed, this.DefinitionIdentity));
|
|
}
|
|
else
|
|
{
|
|
Fx.AssertAndThrow(completionState == ActivityInstanceState.Canceled, "Cannot be executing a workflow instance when WorkflowState was completed.");
|
|
this.controller.Track(new WorkflowInstanceRecord(this.Id, this.WorkflowDefinition.DisplayName, WorkflowInstanceStates.Canceled, this.DefinitionIdentity));
|
|
}
|
|
this.hasTrackedCompletion = true;
|
|
}
|
|
}
|
|
|
|
void TrackResumed()
|
|
{
|
|
// track if necessary
|
|
if (!this.hasTrackedResumed)
|
|
{
|
|
if (this.Controller.TrackingEnabled)
|
|
{
|
|
if (!this.HasTrackedStarted)
|
|
{
|
|
this.TrackingProvider.AddRecord(new WorkflowInstanceRecord(this.Id, this.WorkflowDefinition.DisplayName, WorkflowInstanceStates.Started, this.DefinitionIdentity));
|
|
this.HasTrackedStarted = true;
|
|
}
|
|
else
|
|
{
|
|
this.TrackingProvider.AddRecord(new WorkflowInstanceRecord(this.Id, this.WorkflowDefinition.DisplayName, WorkflowInstanceStates.Resumed, this.DefinitionIdentity));
|
|
}
|
|
}
|
|
this.hasTrackedResumed = true;
|
|
}
|
|
}
|
|
|
|
void Run()
|
|
{
|
|
// validate we're in an ok state
|
|
ThrowIfAborted();
|
|
|
|
TrackResumed();
|
|
|
|
// and let the scheduler go
|
|
this.executor.MarkSchedulerRunning();
|
|
}
|
|
|
|
void ScheduleCancel()
|
|
{
|
|
// validate we're in an ok state
|
|
ThrowIfAborted();
|
|
|
|
TrackResumed();
|
|
|
|
this.executor.CancelRootActivity();
|
|
}
|
|
|
|
BookmarkResumptionResult ScheduleBookmarkResumption(Bookmark bookmark, object value)
|
|
{
|
|
// validate we're in an ok state
|
|
ValidateScheduleResumeBookmark();
|
|
|
|
TrackResumed();
|
|
|
|
return this.executor.TryResumeHostBookmark(bookmark, value);
|
|
}
|
|
|
|
BookmarkResumptionResult ScheduleBookmarkResumption(Bookmark bookmark, object value, BookmarkScope scope)
|
|
{
|
|
// validate we're in an ok state
|
|
ValidateScheduleResumeBookmark();
|
|
|
|
TrackResumed();
|
|
|
|
return this.executor.TryResumeBookmark(bookmark, value, scope);
|
|
}
|
|
|
|
|
|
void ThrowIfAborted()
|
|
{
|
|
if (this.isAborted || (this.executor != null && this.executor.IsAbortPending))
|
|
{
|
|
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.WorkflowInstanceAborted(this.Id)));
|
|
}
|
|
}
|
|
|
|
void ThrowIfNotIdle()
|
|
{
|
|
if (!this.executor.IsIdle)
|
|
{
|
|
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.BookmarksOnlyResumableWhileIdle));
|
|
}
|
|
}
|
|
|
|
[SuppressMessage(FxCop.Category.Design, FxCop.Rule.NestedTypesShouldNotBeVisible,
|
|
Justification = "these are effectively protected methods, but encapsulated in a struct to avoid naming conflicts")]
|
|
protected struct WorkflowInstanceControl
|
|
{
|
|
ActivityExecutor executor;
|
|
WorkflowInstance instance;
|
|
|
|
internal WorkflowInstanceControl(WorkflowInstance instance, ActivityExecutor executor)
|
|
{
|
|
this.instance = instance;
|
|
this.executor = executor;
|
|
}
|
|
|
|
public bool IsPersistable
|
|
{
|
|
get
|
|
{
|
|
return this.executor.IsPersistable;
|
|
}
|
|
}
|
|
|
|
public bool HasPendingTrackingRecords
|
|
{
|
|
get
|
|
{
|
|
return this.instance.HasTrackingParticipant && this.instance.TrackingProvider.HasPendingRecords;
|
|
}
|
|
}
|
|
|
|
public bool TrackingEnabled
|
|
{
|
|
get
|
|
{
|
|
return this.instance.HasTrackingParticipant && this.instance.TrackingProvider.ShouldTrackWorkflowInstanceRecords;
|
|
}
|
|
}
|
|
|
|
public WorkflowInstanceState State
|
|
{
|
|
get
|
|
{
|
|
WorkflowInstanceState result;
|
|
|
|
if (this.instance.isAborted)
|
|
{
|
|
result = WorkflowInstanceState.Aborted;
|
|
}
|
|
else if (!this.executor.IsIdle)
|
|
{
|
|
result = WorkflowInstanceState.Runnable;
|
|
}
|
|
else
|
|
{
|
|
if (this.executor.State == ActivityInstanceState.Executing)
|
|
{
|
|
result = WorkflowInstanceState.Idle;
|
|
}
|
|
else
|
|
{
|
|
result = WorkflowInstanceState.Complete;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
if (!(obj is WorkflowInstanceControl))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
WorkflowInstanceControl other = (WorkflowInstanceControl)obj;
|
|
return other.instance == this.instance;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return this.instance.GetHashCode();
|
|
}
|
|
|
|
public static bool operator ==(WorkflowInstanceControl left, WorkflowInstanceControl right)
|
|
{
|
|
return left.Equals(right);
|
|
}
|
|
|
|
public static bool operator !=(WorkflowInstanceControl left, WorkflowInstanceControl right)
|
|
{
|
|
return !left.Equals(right);
|
|
}
|
|
|
|
public ReadOnlyCollection<BookmarkInfo> GetBookmarks()
|
|
{
|
|
bool resetRequired = false;
|
|
|
|
try
|
|
{
|
|
this.instance.StartReadOnlyOperation(ref resetRequired);
|
|
|
|
this.instance.ValidateGetBookmarks();
|
|
|
|
return this.executor.GetAllBookmarks();
|
|
}
|
|
finally
|
|
{
|
|
this.instance.FinishOperation(ref resetRequired);
|
|
}
|
|
}
|
|
|
|
public ReadOnlyCollection<BookmarkInfo> GetBookmarks(BookmarkScope scope)
|
|
{
|
|
bool resetRequired = false;
|
|
|
|
try
|
|
{
|
|
this.instance.StartReadOnlyOperation(ref resetRequired);
|
|
|
|
this.instance.ValidateGetBookmarks();
|
|
|
|
return this.executor.GetBookmarks(scope);
|
|
}
|
|
finally
|
|
{
|
|
this.instance.FinishOperation(ref resetRequired);
|
|
}
|
|
}
|
|
|
|
public IDictionary<string, LocationInfo> GetMappedVariables()
|
|
{
|
|
bool resetRequired = false;
|
|
|
|
try
|
|
{
|
|
this.instance.StartReadOnlyOperation(ref resetRequired);
|
|
|
|
this.instance.ValidateGetMappedVariables();
|
|
|
|
IDictionary<string, LocationInfo> mappedLocations = this.instance.executor.GatherMappableVariables();
|
|
if (mappedLocations != null)
|
|
{
|
|
mappedLocations = new ReadOnlyDictionaryInternal<string, LocationInfo>(mappedLocations);
|
|
}
|
|
else
|
|
{
|
|
mappedLocations = WorkflowInstance.EmptyMappedVariablesDictionary;
|
|
}
|
|
return mappedLocations;
|
|
}
|
|
finally
|
|
{
|
|
this.instance.FinishOperation(ref resetRequired);
|
|
}
|
|
}
|
|
|
|
public void Run()
|
|
{
|
|
bool resetRequired = false;
|
|
|
|
try
|
|
{
|
|
this.instance.StartOperation(ref resetRequired);
|
|
|
|
this.instance.Run();
|
|
}
|
|
finally
|
|
{
|
|
this.instance.FinishOperation(ref resetRequired);
|
|
}
|
|
|
|
this.executor.Run();
|
|
}
|
|
|
|
public void RequestPause()
|
|
{
|
|
// No validations for this because we do not
|
|
// require calls to Pause to be synchronized
|
|
// by the caller
|
|
this.executor.PauseScheduler();
|
|
}
|
|
|
|
// Calls Pause when IsPersistable goes from false->true
|
|
public void PauseWhenPersistable()
|
|
{
|
|
bool resetRequired = false;
|
|
|
|
try
|
|
{
|
|
this.instance.StartOperation(ref resetRequired);
|
|
|
|
this.instance.ValidatePauseWhenPersistable();
|
|
|
|
this.executor.PauseWhenPersistable();
|
|
}
|
|
finally
|
|
{
|
|
this.instance.FinishOperation(ref resetRequired);
|
|
}
|
|
}
|
|
|
|
public void ScheduleCancel()
|
|
{
|
|
bool resetRequired = false;
|
|
|
|
try
|
|
{
|
|
this.instance.StartOperation(ref resetRequired);
|
|
|
|
this.instance.ScheduleCancel();
|
|
}
|
|
finally
|
|
{
|
|
this.instance.FinishOperation(ref resetRequired);
|
|
}
|
|
}
|
|
|
|
public void Terminate(Exception reason)
|
|
{
|
|
bool resetRequired = false;
|
|
|
|
try
|
|
{
|
|
this.instance.StartOperation(ref resetRequired);
|
|
|
|
this.instance.Terminate(reason);
|
|
}
|
|
finally
|
|
{
|
|
this.instance.FinishOperation(ref resetRequired);
|
|
}
|
|
}
|
|
|
|
public BookmarkResumptionResult ScheduleBookmarkResumption(Bookmark bookmark, object value)
|
|
{
|
|
bool resetRequired = false;
|
|
|
|
try
|
|
{
|
|
this.instance.StartOperation(ref resetRequired);
|
|
|
|
return this.instance.ScheduleBookmarkResumption(bookmark, value);
|
|
}
|
|
finally
|
|
{
|
|
this.instance.FinishOperation(ref resetRequired);
|
|
}
|
|
}
|
|
|
|
public BookmarkResumptionResult ScheduleBookmarkResumption(Bookmark bookmark, object value, BookmarkScope scope)
|
|
{
|
|
bool resetRequired = false;
|
|
|
|
try
|
|
{
|
|
this.instance.StartOperation(ref resetRequired);
|
|
|
|
return this.instance.ScheduleBookmarkResumption(bookmark, value, scope);
|
|
}
|
|
finally
|
|
{
|
|
this.instance.FinishOperation(ref resetRequired);
|
|
}
|
|
}
|
|
|
|
public void Abort()
|
|
{
|
|
bool resetRequired = false;
|
|
|
|
try
|
|
{
|
|
this.instance.StartOperation(ref resetRequired);
|
|
|
|
// No validations
|
|
|
|
this.executor.Dispose();
|
|
|
|
this.instance.Abort(null);
|
|
}
|
|
finally
|
|
{
|
|
this.instance.FinishOperation(ref resetRequired);
|
|
}
|
|
}
|
|
|
|
public void Abort(Exception reason)
|
|
{
|
|
bool resetRequired = false;
|
|
|
|
try
|
|
{
|
|
this.instance.StartOperation(ref resetRequired);
|
|
|
|
// No validations
|
|
|
|
this.executor.Abort(reason);
|
|
|
|
this.instance.Abort(reason);
|
|
}
|
|
finally
|
|
{
|
|
this.instance.FinishOperation(ref resetRequired);
|
|
}
|
|
}
|
|
|
|
[SuppressMessage(FxCop.Category.Design, FxCop.Rule.ConsiderPassingBaseTypesAsParameters,
|
|
Justification = "Only want to allow WorkflowInstanceRecord subclasses for WorkflowInstance-level tracking")]
|
|
public void Track(WorkflowInstanceRecord instanceRecord)
|
|
{
|
|
if (this.instance.HasTrackingParticipant)
|
|
{
|
|
this.instance.TrackingProvider.AddRecord(instanceRecord);
|
|
}
|
|
}
|
|
|
|
public void FlushTrackingRecords(TimeSpan timeout)
|
|
{
|
|
this.instance.FlushTrackingRecords(timeout);
|
|
}
|
|
|
|
public IAsyncResult BeginFlushTrackingRecords(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
return this.instance.BeginFlushTrackingRecords(timeout, callback, state);
|
|
}
|
|
|
|
public void EndFlushTrackingRecords(IAsyncResult result)
|
|
{
|
|
this.instance.EndFlushTrackingRecords(result);
|
|
}
|
|
|
|
public object PrepareForSerialization()
|
|
{
|
|
bool resetRequired = false;
|
|
|
|
try
|
|
{
|
|
this.instance.StartReadOnlyOperation(ref resetRequired);
|
|
|
|
this.instance.ValidatePrepareForSerialization();
|
|
|
|
return this.executor.PrepareForSerialization();
|
|
}
|
|
finally
|
|
{
|
|
this.instance.FinishOperation(ref resetRequired);
|
|
}
|
|
}
|
|
|
|
public ActivityInstanceState GetCompletionState()
|
|
{
|
|
return this.executor.State;
|
|
}
|
|
|
|
[SuppressMessage(FxCop.Category.Design, FxCop.Rule.AvoidOutParameters,
|
|
Justification = "Arch approved design. Requires the out argument for extra information provided")]
|
|
public ActivityInstanceState GetCompletionState(out Exception terminationException)
|
|
{
|
|
terminationException = this.executor.TerminationException;
|
|
return this.executor.State;
|
|
}
|
|
|
|
[SuppressMessage(FxCop.Category.Design, FxCop.Rule.AvoidOutParameters,
|
|
Justification = "Arch approved design. Requires the out argument for extra information provided")]
|
|
public ActivityInstanceState GetCompletionState(out IDictionary<string, object> outputs, out Exception terminationException)
|
|
{
|
|
outputs = this.executor.WorkflowOutputs;
|
|
terminationException = this.executor.TerminationException;
|
|
return this.executor.State;
|
|
}
|
|
|
|
public Exception GetAbortReason()
|
|
{
|
|
return this.instance.abortedException;
|
|
}
|
|
}
|
|
}
|
|
}
|