Updating the Retry Activity - adding timed interval

by Matt Milner 28. November 2007 14:19

Updated 11/29 - see below

I recently created a retry activity that I've been wanting to write for some time now, and had a few features that I wanted to add. I was teaching a class a couple of weeks ago and took the opportunity to add a retry interval to the activity.  The activity is designed as a composite activity which can retry its child activities if it detects that a fault/exception has occurred.  Rather than retrying immediately, I wanted the consumer of my activity to be able to specify a TimeSpan indicating how long to wait between retry attempts.

The simplest part was adding a property that allowed people to specify the interval. 

private TimeSpan retryInt;

public TimeSpan RetryInterval 
{
    get { return retryInt; }
    set { retryInt = value; }
}

 

The next step was to actually use this information.  When the child activity closes, my Retry activity checks if it faulted and then needs to reschedule the child activity for execution.  With the interval, I need a way to wait for a duration and then schedule the activity for execution.  Since my workflow could be persisted during this interval, I have to use a mechanism that will both support passivation, and allow for the workflow to be resumed automatically when the duration has been reached.  To support this I used the same mechanism that the delay activity uses, which is to create  timer subscription and then wait for an event signaling the time has expired. 

In my event handler for closing child activities, if I determine that I should retry the child activity, I use the following code to 1) create a new workflowqueue where the timer can signal completion, 2)register for data arriving on the queue, 3) create a TimerEventSubscription with my duration and my queue information and 4) add my timer subscription the collection on the workflow. 

 

DateTime expires = DateTime.UtcNow.Add(RetryInterval);
SubscriptionID = Guid.NewGuid();
thisContext.GetService<WorkflowQueuingService>().CreateWorkflowQueue(SubscriptionID, false).QueueItemAvailable += new EventHandler<QueueEventArgs>(RetryActivity_QueueItemAvailable);
TimerEventSubscription subscription = new TimerEventSubscription(SubscriptionID, WorkflowInstanceId, expires);
TimerEventSubscriptionCollection timers = GetTimerSubscriptionCollection();
timers.Add(subscription);

 

The GetTimerSubscriptionCollection simply grabs the collection from the parent workflow by recursively climbing the parent hierarchy. 

 

private TimerEventSubscriptionCollection GetTimerSubscriptionCollection()
{
    Activity parent = this;
    while (parent.Parent != null)
    {
        parent = parent.Parent;
    }
    TimerEventSubscriptionCollection timers = (TimerEventSubscriptionCollection)parent.GetValue(TimerEventSubscriptionCollection.TimerCollectionProperty);
    return timers;
}

 

Finally, I have the event handler for when data arrives on the queue for the timer.  I simply empty and delete the queue, then begin my next iteration on the child activity. 

 

void RetryActivity_QueueItemAvailable(object sender, QueueEventArgs e)
{
    ActivityExecutionContext ctx = sender as ActivityExecutionContext;
    ctx.GetService<WorkflowQueuingService>().GetWorkflowQueue(e.QueueName).Dequeue();
    ctx.GetService<WorkflowQueuingService>().DeleteWorkflowQueue(e.QueueName);

    BeginIteration(ctx);
}

 

So adding a retry interval turned out to be pretty simple and I was also able to provide a good example of how you can put your own delay into your activities.  In a later post, I'm going to talk about the other issue I had to manage which is how to deal with exceptions and control whether or not my Retry activity got put into the faulting state. 

 

BEGIN UPDATE

Someone posted a question on the original activity indicating some concern over the fact that I hadn't overridden the Cancel method.  In the simpler scenario I should have still done that, but with the addition of my retry interval it became even more important as I had more to clean up.  So I added the Cancel method and do two things in it.  First, I had to see if there were any spawned execution contexts that were currently executing indicating that I had an iteration in progress and cancel the root activity if I did.  I also had to add some code to remove the timersubscription in both the expected case and the exception case.  In the process, I factored some code into a helper method to remove the timer subscription and the workflow queue when I no longer needed them.  The cancel method now looks like this:

protected override ActivityExecutionStatus Cancel(ActivityExecutionContext executionContext)
{
    bool canClose = true;
    ActivityExecutionContext ctx = executionContext;
    //cancel any execution iteration
    if (ctx.ExecutionContextManager.ExecutionContexts.Count > 0)
    {
        ctx.ExecutionContextManager.ExecutionContexts[0].CancelActivity(ctx.ExecutionContextManager.ExecutionContexts[0].Activity);
        canClose = false;
    }
    //remove the timer and related queue if there is a subscription
    CleanupSubscription(ctx);
    return canClose ? ActivityExecutionStatus.Closed : ActivityExecutionStatus.Canceling;
}
Notice that I can only close the activity if the child is not currently executing.  If I had to cancel the child activity, then I can't yet close the retry, that will happen in the usual way when the child activity closes.  The other important step I added was to check the current ExecutionStatus of the retry activity before scheduling a new iteration.  If the activity has moved into the Canceling state, I shouldn't start a new iteration regardless of how many times I am supposed to retry. 

If you have any other feedback or suggested improvements, keep them coming. 

END UPDATE

The updated retry activity can be found here, and has been updated for Visual Studio 2008.  If you want the updates for 2005, just download the original, and then copy the class file for the retry activity from the new project.  I didn't use any new features of 2008, just updated the project and solution files, so it should work fine. 

Tags:

Windows Workflow Foundation

Visual Studio 2008 released!

by Matt Milner 19. November 2007 11:25

Hot off the presses, you can get it now from MSDN subscriber downloads or the trial version.  See Soma's blog for more details on the new features, etc. 

Surprisingly, being a WF and BizTalk guy, I'm least excited about the WF/WCF integration.  I'll talk more about what I dislike later, but this is a happy post!  I'm jazzed about the new web models in WCF if only because it provides a nice affirmation of the extensibility of WCF.  I might not like all of the details of how they have accomplished things, but the CSD team has certainly shown that WCF is a solid platform with all the extensibility hooks you need. 

I'm also excited to spend some time with LINQ now that I'll have the bits on my main PC (I have only put the beta on VMs or my secondary machine in my office).  Like everyone else, I've done the simple examples to get some data out of a database and bind it to a UI, but I really want to dig in and see how it works after I have some code written and want to change something in the database. I want to see what the visual modeler/designer can and cannot do, and how using XML files might benefit me more than using the simple path that is there in V1.  

The main thing that really sells this, and some folks don't know this, is that VS 2008 and .NET 3.5, build on the existing .NET 2.0 framework.  So, it should be close to a no-brainer for organizations to upgrade because they can still build their 2.0 applications without having to worry about using 3.5 assemblies if they use the multi-targeting correctly in VS / MS Build.  That's right, there are only additive changes and bug fixes in 3.5 and it uses the same 2.0 runtime that you are using today.  So there should be no issues with breaking changes to core things like datasets and remoting like there were moving from 1.1 to 2.0.  I'm upgrading all my boxes to take advantage of the new features and perf improvements.  The only reason I could see for not updating is if you have other tools like BizTalk or Commerce Server that depend on the 2005 version.  Then you can run both side-by-side, and maybe strip out some of the tools from 2005 that you no longer used to free up some space on your hard drive.   

So, download, enjoy, and happy coding! 

Tags:

General Musings | Windows Workflow Foundation | Windows Communication Foundation

Pluralsight Developer Training

View my developer training videos on Pluralsight.

Pluralsight .NET Training

Tag cloud