Doears
Please help me with restoring pending (and saved) workflows.
I am trying to check a self-service workflow repository to see if there is any instance that has been delayed and can be resumed. For testing purposes, I created a fictitious activity that is delayed, and it persists with a delay.
Typically, resuming a process is as follows:
get WF definition configure sql instance store call WaitForEvents is there event with HasRunnableWorkflowEvent.Value name and if it is create WorkflowApplication object and execute LoadRunnableInstance method
it works fine if created|initialized , WaitForEvents is stored, saving is closed. In this case, the repository reads all available workflows from the saved database and throws a timeout exception if there are no workflows to resume.
The problem occurs if the repository is created, and the loop starts only for WaitForEvents (the same thing happens with BeginWaitForEvents ). In this case, it reads all available workflows from the DB (with the corresponding identifiers), but instead of a timeout exception it will read another instance (I know for sure how many workflows there are ready to resume, because using a separate database test). But not readable, but throws InstanceNotReadyException . In catch I check workflowApplication.Id , but before it was not saved with my test.
I tried to start a new (empty) persistent database and the result would be the same: (
This code does not work:
using (var storeWrapper = new StoreWrapper(wf, connStr)) for (int q = 0; q < 5; q++) { var id = Resume(storeWrapper);
But this works as expected:
for (int q = 0; q < 5; q++) using (var storeWrapper = new StoreWrapper(wf, connStr)) { var id = Resume(storeWrapper);
What is the best solution in this case? Add empty catch for InstanceNotReadyException and ignore it?
Here are my tests
const int delayTime = 15; string connStr = "Server=db;Database=AppFabricDb_Test;Integrated Security=True;"; [TestMethod] public void PersistOneOnIdleAndResume() { var wf = GetDelayActivity(); using (var storeWrapper = new StoreWrapper(wf, connStr)) { var id = CreateAndRun(storeWrapper); Trace.WriteLine(string.Format("done {0}", id)); } using (var storeWrapper = new StoreWrapper(wf, connStr)) for (int q = 0; q < 5; q++) { var id = Resume(storeWrapper); Trace.WriteLine(string.Format("resumed {0}", id)); } } Activity GetDelayActivity(string addName = "") { var name = new Variable<string>(string.Format("incr{0}", addName)); Activity wf = new Sequence { DisplayName = "testDelayActivity", Variables = { name, new Variable<string>("CustomDataContext") }, Activities = { new WriteLine { Text = string.Format("before delay {0}", delayTime) }, new Delay { Duration = new InArgument<TimeSpan>(new TimeSpan(0, 0, delayTime)) }, new WriteLine { Text = "after delay" } } }; return wf; } Guid CreateAndRun(StoreWrapper sw) { var idleEvent = new AutoResetEvent(false); var wfApp = sw.GetApplication(); wfApp.Idle = e => idleEvent.Set(); wfApp.Aborted = e => idleEvent.Set(); wfApp.Completed = e => idleEvent.Set(); wfApp.Run(); idleEvent.WaitOne(40 * 1000); var res = wfApp.Id; wfApp.Unload(); return res; } Guid Resume(StoreWrapper sw) { var res = Guid.Empty; var events = sw.GetStore().WaitForEvents(sw.Handle, new TimeSpan(0, 0, delayTime)); if (events.Any(e => e.Equals(HasRunnableWorkflowEvent.Value))) { var idleEvent = new AutoResetEvent(false); var obj = sw.GetApplication(); try { obj.LoadRunnableInstance();
Here is the definition of the storage container that I use for the test:
public class StoreWrapper : IDisposable { Activity WfDefinition { get; set; } public static readonly XName WorkflowHostTypePropertyName = XNamespace.Get("urn:schemas-microsoft-com:System.Activities/4.0/properties").GetName("WorkflowHostType"); public StoreWrapper(Activity wfDefinition, string connectionStr) { _store = new SqlWorkflowInstanceStore(connectionStr); HostTypeName = XName.Get(wfDefinition.DisplayName, "ttt.workflow"); WfDefinition = wfDefinition; } SqlWorkflowInstanceStore _store; public SqlWorkflowInstanceStore GetStore() { if (Handle == null) { InitStore(_store, WfDefinition); Handle = _store.CreateInstanceHandle(); var view = _store.Execute(Handle, new CreateWorkflowOwnerCommand { InstanceOwnerMetadata = { { WorkflowHostTypePropertyName, new InstanceValue(HostTypeName) } } }, TimeSpan.FromSeconds(30)); _store.DefaultInstanceOwner = view.InstanceOwner;