Workflows and no persist zones

There are times when a workflow can’t be persisted safely using a SqlWorkflowInstanceStore. The reason isn’t so much saving the state of  a workflow to disk, that could be done at any time, but the result when a workflow would be reloaded from disk in that state.

An easy example is a workflow handling a WCF request with a Receive and SendReply activity pair. Suppose you would save the workflow state after the message had been received but before the response had been send. No problem there. Now suppose the workflow is aborted just after the response is send and later the saved state is reloaded. The first action of the reloaded workflow would be to send the response again. But where? The connection is long gone and the client has seen the original response and is happy. Clearly this would fault the workflow again and for that reason the workflow is in a no persist zone starting at the Receive and ending at the SendReply activity.

But wait. The SendReply activity has a property named PersistBeforeSend, surely that means it will be persisted before the reply is send? Actually it doesn’t and it persists after the response has been send. So the property name is wrong? Not quite, although I believe it is poorly named, as the Persist activity is actually scheduled just before the response is send. But due to the asynchronous nature of workflow execution it doesn’t execute until after the response has actually been send and we are at a point where it would be save to reload the workflow.

So what happens of you try to persist a workflow in a no persist zone? You will get an exception with the following message:

Persist activities cannot be contained within no persistence blocks.

In this case all I did was drop a Persist activity between a Receive and SendReply activity pair, I didn’t even add or configure the SqlWorkflowInstanceStore itself.

 

Checking for a no persist zone

So how can we check if we are currently in a no persist zone? It turns out the NativeActivityContext has a property called IsInNoPersistScope that will tell us that, unfortunately the property is internal so we can’t use it [:(].

Turns out that duplicating this behavior is quite easy though. Whenever a no persist zone is started a NoPersistProperty is created and stored in the NativeActivityContext Properties collection. The NoPersistProperty is also internal but that is no problem as all we need to do if check if there is a object stored under the name "System.Activities.NoPersistProperty", something we can do with the following activity:

public class CheckForNoPersistZone : NativeActivity


{


    public OutArgument<bool> IsInNoPersistScope { get; set; }


 


    protected override void Execute(NativeActivityContext context)


    {


        var prop = context.Properties.Find("System.Activities.NoPersistProperty");


        IsInNoPersistScope.Set(context, prop != null);


    }


}

 

Creating our own no persist zones

Sometimes it might be required to stop a workflow from persisting and we can do so by creating our own no persist zone. There is no standard activity to do so but creating a pair of activities to do so isn’t hard. The basic building blocks are a NoPersistHandle and calling Enter() and Exit() on it to start and end the no persist zone. This could easily be done through a composite activity or through two separate activities. In the code below I have chosen for the approach with a single composite activities.

public class NoPersistZone : NativeActivity


{


    private Variable<NoPersistHandle> NoPersistHandle { get; set; }


    


    [RequiredArgument]


    public Activity Body { get; set; }


 


    protected override void CacheMetadata(NativeActivityMetadata metadata)


    {


        NoPersistHandle = new Variable<NoPersistHandle>();


        metadata.AddImplementationVariable(NoPersistHandle);


 


        base.CacheMetadata(metadata);


    }


 


    protected override void Execute(NativeActivityContext context)


    {


        var noPersistHandle = NoPersistHandle.Get(context);


        noPersistHandle.Enter(context);


        context.ScheduleActivity(Body, OnCompleted);


    }


 


    private void OnCompleted(NativeActivityContext context, ActivityInstance completedInstance)


    {


        var noPersistHandle = NoPersistHandle.Get(context);


        noPersistHandle.Exit(context);


    }


}





 



Another nice activity to have for a generic toolbox.



 



Enjoy!



[f1]
[f2]

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>