SqlWorkflowInstanceStore WaitForEvents returns HasRunnableWorkflowEvent, but LoadRunnableInstance does not work

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); // InstanceNotReadyException here when all activities is resumed 

But this works as expected:

 for (int q = 0; q < 5; q++) using (var storeWrapper = new StoreWrapper(wf, connStr)) { var id = Resume(storeWrapper); // timeout exception here or beginWaitForEvents continues to wait 

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(); //instancenotready here if the same store has read all instances from DB and no delayed left obj.Idle = e => idleEvent.Set(); obj.Completed = e => idleEvent.Set(); obj.Run(); idleEvent.WaitOne(40 * 1000); res = obj.Id; obj.Unload(); } catch (InstanceNotReadyException) { Trace.TraceError("failed to resume {0} {1} {2}", obj.Id , obj.DefinitionIdentity == null ? null : obj.DefinitionIdentity.Name , obj.DefinitionIdentity == null ? null : obj.DefinitionIdentity.Version); foreach (var e in events) { Trace.TraceWarning("event {0}", e.Name); } throw; } } return res; } 

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; //Trace.WriteLine(string.Format("{0} owns {1}", view.InstanceOwner.InstanceOwnerId, HostTypeName)); } return _store; } protected virtual void InitStore(SqlWorkflowInstanceStore store, Activity wfDefinition) { } public InstanceHandle Handle { get; protected set; } XName HostTypeName { get; set; } public void Dispose() { if (Handle != null) { var deleteOwner = new DeleteWorkflowOwnerCommand(); //Trace.WriteLine(string.Format("{0} frees {1}", Store.DefaultInstanceOwner.InstanceOwnerId, HostTypeName)); _store.Execute(Handle, deleteOwner, TimeSpan.FromSeconds(30)); Handle.Free(); Handle = null; _store = null; } } public WorkflowApplication GetApplication() { var wfApp = new WorkflowApplication(WfDefinition); wfApp.InstanceStore = GetStore(); wfApp.PersistableIdle = e => PersistableIdleAction.Persist; Dictionary<XName, object> wfScope = new Dictionary<XName, object> { { WorkflowHostTypePropertyName, HostTypeName } }; wfApp.AddInitialInstanceValues(wfScope); return wfApp; } } 
+5
source share
1 answer

I am not a specialist in the basics of the workflow, so my answer is based on official Microsoft examples. The first - the WF4 host resumes the slowed-down workflow (CSWF4LongRunningHost) , and the second Microsoft.Samples.AbsoluteDelay . In both examples you will find code similar to your ie:

 try { wfApp.LoadRunnableInstance(); ... } catch (InstanceNotReadyException) { //Some logging } 

Bearing this in mind, the answer is that you are correct, and an empty catch for InstanceNotReadyException is a good solution.

0
source

Source: https://habr.com/ru/post/1242386/


All Articles