DenisMikhalkin

I am playing with persistence service. I have a sequential workflow of three elements - code, delay, code:

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
Console.WriteLine("Code executed");
TrackData("Executed code", "Code executed");
Timer timer = new Timer(UnLoadWorkflow, null, 0, Timeout.Infinite);
}
private void codeActivity2_ExecuteCode(object sender, EventArgs e)
{
Console.WriteLine("Workflow continued " + DateTime.Now);
}
private static void UnLoadWorkflow(object state)
{
Console.WriteLine("Unloading workflow " + Program.Tstmp());
Program.instance.Unload();
Timer timer = new Timer(LoadWorkflow, null, 3000, Timeout.Infinite);
}

private static void LoadWorkflow(object state) {
Console.WriteLine("Loading workflow " + Program.Tstmp());
Program.loading = true;
Program.instance.Load();
}

The delay is for 10 seconds. As you see, when first code is executed, it starts timer which unloads workflow almost immediately. After three seconds, it loads it back. My persistence service is configured like that:
workflowRuntime.AddService(new SqlWorkflowPersistenceService(ConfigurationManager.ConnectionStrings["WorkflowDatabase"].ConnectionString));

As you can see, no UnloadOnIdle parameter.

Now, what happens is that sometimes workflow loads by itself, not from my timer. Here is the stack trace:

> WorkflowConsoleApplication1.exe!WorkflowConsoleApplication1.Program.workflowRuntime_WorkflowLoaded(object sender = {System.Workflow.Runtime.WorkflowRuntime}, System.Workflow.Runtime.WorkflowEventArgs e = {System.Workflow.Runtime.WorkflowEventArgs}) Line 75 + 0x12 bytes C#
System.Workflow.Runtime.dll!System.Workflow.Runtime.WorkflowRuntime._OnServiceEvent(System.Workflow.Runtime.WorkflowExecutor sched = {System.Workflow.Runtime.WorkflowExecutor}, bool unregister = false, System.EventHandler<System.Workflow.Runtime.WorkflowEventArgs> handler) + 0x43 bytes
System.Workflow.Runtime.dll!System.Workflow.Runtime.WorkflowRuntime.OnScheduleLoaded(System.Workflow.Runtime.WorkflowExecutor schedule) + 0x81 bytes
System.Workflow.Runtime.dll!System.Workflow.Runtime.WorkflowRuntime.WorkflowExecutionEvent(object sender, System.Workflow.Runtime.WorkflowExecutor.WorkflowExecutionEventArgs e) + 0x97 bytes
[Native to Managed Transition]
[Managed to Native Transition]
System.Workflow.Runtime.dll!System.Workflow.Runtime.WorkflowExecutor.FireWorkflowExecutionEvent(object sender, System.Workflow.Runtime.WorkflowEventInternal eventType) + 0x32 bytes
System.Workflow.Runtime.dll!System.Workflow.Runtime.WorkflowExecutor.Registered(bool isActivation) + 0x96 bytes
System.Workflow.Runtime.dll!System.Workflow.Runtime.WorkflowRuntime.Load(System.Guid key, System.Workflow.Runtime.CreationContext context, System.Workflow.Runtime.WorkflowInstance workflowInstance) + 0x28c bytes
System.Workflow.Runtime.dll!System.Workflow.Runtime.WorkflowRuntime.GetWorkflow(System.Guid instanceId) + 0x95 bytes
System.Workflow.Runtime.dll!System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService.LoadWorkflowsWithExpiredTimers(object ignored) + 0xc9 bytes
System.Workflow.Runtime.dll!System.Workflow.Runtime.Hosting.SmartTimer.HandleCallback(object state) + 0x2a bytes
mscorlib.dll!System.Threading._TimerCallback.TimerCallback_Context(object state) + 0x1a bytes
mscorlib.dll!System.Threading.ExecutionContext.runTryCode(object userData) + 0x43 bytes
[Native to Managed Transition]
[Managed to Native Transition]
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0xa7 bytes
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x92 bytes
mscorlib.dll!System.Threading._TimerCallback.PerformTimerCallback(object state) + 0x5b bytes

The key line that I think is a clue is this:
System.Workflow.Runtime.dll!System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService.LoadWorkflowsWithExpiredTimers(object ignored) + 0xc9 bytes

I suppose this happens because the Idle activity got executed, set a timer for the end of idle period and that timer checks if workflow was unloaded by persistence service and restores it if it was. If that is so, the problem I see is that this logic is good for UnloadOnIdle, and is responsible for automatic loading of unloaded workflows, but it conflicts with what I wanted to do.

What I want is to be able to control my workflows. I tell them when to load or unload. But what happens is that (presumably) if workflow have an Idle activity, they sometimes decide whether to load by themselves. Or is it because of something else

Is it a bug Is there a way to disable this behavior or workaround it in some way

Thanks.

Denis


Re: Windows Workflow Foundation Bug or featu Workflow instance loads by itself after being unloaded

Konstantin Vyaznikov

Hi Denis,

I am not sure I understand your point. As I got you want to exclusively control loading and unloading of workflows. That's fine. But then when you have a delay activity in workflow and standard persistence service, you essentially say to load workflow when timeout is expired - that's how delay activity behaves altogether with standard persistence service.

So you see you have a conflict. If you really want load/unload by yourself, you need to change persistence service and exclude functionality of loading workflows with expired timers. In this case, you can load workflow when you want. But in this case, you cannot shutdown workflow host, because your timer of loading workflow will disappear. So, you would need to store information of what and when to load somewhere in reliable place (DB, file, etc). Also keep in mind that you can unload only workflows which are in 'idle' state, i.e. if Activity in workflow returned ActivityExecutionStatus.Executing.

Thanks,

Konstantin.





Re: Windows Workflow Foundation Bug or featu Workflow instance loads by itself after being unloaded

DenisMikhalkin

First, let me clear a confusion - when I wrote "idle activity" I actually meant "Delay activity". Hope that clarifies a few things.

Now, on Delay activity - I couldn't find any clues in the documentation which would say that Delay activity has any side effects, for example, that it causes a workflow to be loaded after the specified period of time.

Which makes things quite undetermenistic - imagine if someone designed workflows and put Delay activities in them, stored them somewhere and now I am implementing host. If I don't think ahead about the fact that they might have used Delay activities, I might be in situation that an activity which I unloaded (for example, before shutdown) suddenly woke up.

Think about Delay activitiy as a pause in a workflow execution (which it is by definition from the documentation). Naturally, between any two consecutive activities in a workflow there is a such a delay, quite short though, but still, which is the time that it takes for the runtime to switch from one activity to another. Now, Delay activity is just a prolongation of this delay. That's how I see it, and I don't see why I or anyone else should be thinking otherwise. Any person taking this activity from a toolbox should not be required to think "or, but what would happen to that piece of the system which has hidden connection with this activity" Imagine if workflows were designed or even worse, auto-generate, by non-software engineers.

Now, a simple workaround would be to write your own Delay activity which does everything that system Delay activity does but doesn't cause workflow to load if it is not instructed to do so. This goes along with the idea of explicitly specifying for SqlWorkflowPersistenceService that it should unload workflows automatically if they become idle (UnloadOnIdle parameter). Or least if SqlWorkflowPersistenceService.LoadExpiredTimerWorkflowIds() were virtual...

I noticed that the documentation for SqlWorkflowPersistenceService mentions DelayActivity and timers. If that's what is causing this effect, then I would still argue that there should be a property or parameter that would allow disabling this functionality. But ideally, SqlWorkflowPersistenceService simply shouldn't load workflow instances which it didn't unload in the first place!

As for the timers, it should be a responsibility of Delay activity to store its semantic information, such as start time and delay duration, and act accordingly when it loads. In my case, writing a new persistence service is an overkill - I don't care about those timers that Delay activity creates, because I am trying to disable them. So it would be easier simply to change the Delay activity.




Re: Windows Workflow Foundation Bug or featu Workflow instance loads by itself after being unloaded

Konstantin Vyaznikov

Ok. Then answering your direct question: It is a feature.

If you want to achieve functionality you want, you do not need to write your own 'Delay' activity. You can configure persistence service by setting LoadingInterval to 0. Then functionality of loading workflows after expiration time will be disabled. (Not tested though).

Thanks,

Konstantin.