Locking in SqlWorkflowPersistenceService

In my previous post I was thinking about persisted workflows in a server farm and thinking about how optimistic and pessimistic locking affects the operation of workflows in a server farm.

I said that the default behavior of the SqlWorkflowPersistenceService is to use pessimistic locking.  I was partially correct.

It turns out that the default behavior varies depending upon the constructor you use.  There are three constructors for the SqlWorkflowPersistenceService class.  For the one with 4 arguments the default is to use pessimistic locking.  For the other two, one defaults to optimistic locking and the other also defaults to optimistic unless you pass the OwnershipTimeoutSeconds parameter see the docs for more info.

 

Getting this behavior right it turns out is critical.  Because persistence is often difficult to predict in a workflow (especially if you have set UnloadOnIdle==true).  If you allow more than one WorkflowRuntime to load a single instance of a Worfklow you run a high risk of the persisted state being overwritten by different running instances which can result in very strange behavior.

For example, in one test I did today I saw the following

Host 1 Host 2
Create workflow "A" which goes idle and is persisted  
  Get All Workflows - find Workflow "A"
Send a message to workflow "A" to continue processing.  Workflow goes idle  
  Sends message to complete workflow. Workflow instance is removed from the database
Sends a message to workflow "A" continue processing but workflow "A" has been removed from the database so the message cannot be delivered  

 

Given what I learned today I'd have to say that I'd be very careful about using workflows with an optimistic locking.  Detecting these types of situations is not that simple. 

 

One other tidbit.  Often people complain about not being able to predict persistence.  It is actually very easy to force a persistence point in your workflow by building a very simple custom activity.

 

Just a few lines of code and then you can drop this baby anywhere on your workflow to force a persistence point.

 

    [PersistOnClose]
    public partial class ForcePersist: Activity
    {
        public ForcePersist()
        {
            InitializeComponent();
        }
    }

 

image