Workflow Foundation 4.5 InstanceLockedException

I am currently experimenting with Workflow Foundation 4.5 in a client, WCF server architecture.

The way i set up the server is by creating a regular WCF Service Application (not a WCF Workflow Service Application) and added a bunch of web methods to start, resume, get states of of entities living in the workflow.

So i have things like :

public class WebService1 : WebService
{
    private const string ConnectionString = "Server=.\\SQLEXPRESS;Initial Catalog=WF45GettingStartedTutorial;Integrated Security=SSPI";
    private SqlWorkflowInstanceStore _store;

    public SqlWorkflowInstanceStore Store
    {
        get
        {
            if (_store == null)
            {
                Debug.WriteLine("Building new store");
                _store = new SqlWorkflowInstanceStore(ConnectionString);
                StateMachineStateTracker.Promote(Store);
            }

            return _store;
        }
    }

    [WebMethod]
    public Guid Start(Guid resourceId)
    {
        var activity = new Activity1(); // My state machine
        var parameters = new Dictionary<string, object> { { "resourceId", resourceId } };
        var wfApp = new WorkflowApplication(activity, parameters, new WorkflowIdentity("v1", new Version(1, 0), string.Empty));
        ConfigureWorkflowApplication(wfApp);
        wfApp.Run();
        return wfApp.Id;
    }

    [WebMethod]
    public void Resume(Guid instanceId, string bookmarkName, object bookmarkParameter)
    {
        var activity = new Activity1();
        var wfApp = new WorkflowApplication(activity, new WorkflowIdentity("v1", new Version(1, 0), string.Empty));
        ConfigureWorkflowApplication(wfApp);
        wfApp.Load(instanceId);
        wfApp.ResumeBookmark(bookmarkName, bookmarkParameter);
    }

    [WebMethod]
    public string GetCurrentState(Guid instanceId)
    {
        var activity = new Activity1();

        // Things get messy here :

        // var instance = WorkflowApplication.GetInstance(instanceId, Store);
        // var wfApp = new WorkflowApplication(activity, instance.DefinitionIdentity);

        // so replaced with :
        var wfApp = new WorkflowApplication(activity, new WorkflowIdentity("v1", new Version(1, 0), string.Empty));

        ConfigureWorkflowApplication(wfApp);
        var trackerInstance = StateMachineStateTracker.LoadInstance(instanceId, wfApp.WorkflowDefinition, ConnectionString);
        if (trackerInstance != null)
        {
            return trackerInstance.CurrentState;
        }

        return string.Empty;
    }

    private void ConfigureWorkflowApplication(WorkflowApplication wfApp)
    {
        // Configure the persistence store.
        wfApp.InstanceStore = Store;

        // State machine tracker taken from http://code.msdn.microsoft.com/windowsdesktop/Windows-Workflow-fee72008
        var tracker = new StateMachineStateTracker(wfApp.WorkflowDefinition);
        wfApp.Extensions.Add(tracker);
        wfApp.Extensions.Add(new StateTrackerPersistenceProvider(tracker)); 

        wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
        {
            Debug.WriteLine("Workflow completed.");
        };

        wfApp.Aborted = delegate(WorkflowApplicationAbortedEventArgs e)
        {
            Debug.WriteLine(string.Format("Workflow Aborted. Exception: {0}\r\n{1}",
                    e.Reason.GetType().FullName,
                    e.Reason.Message));
        };

        wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
            {
                Debug.WriteLine(string.Format("Unhandled Exception: {0}\r\n{1}",
                                              e.UnhandledException.GetType().FullName,
                                              e.UnhandledException.Message));
            return UnhandledExceptionAction.Terminate;
        };

        wfApp.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs e)
        {
            return PersistableIdleAction.Unload;
        };
    }
}

Now, what i want is to get the workflow version from the instance id, so in my GetCurrentState i call :

var instance = WorkflowApplication.GetInstance(instanceId, Store);

And load the WorkflowApplication with that

var wfApp = new WorkflowApplication(activity, instance.DefinitionIdentity)

this works fine, but i suspect somehow leaves the host of my workflow loaded, and my instance locked.

So when right after calling GetCurrentState(), the client calls Resume() i get the following exception (at the wfApp.Load(instanceId) line).

InstanceLockedException

The execution of an InstancePersistenceCommand was interrupted because the instance 'a1bcbd11-50fc-4a72-b5d2-87b71d0c3c45' is locked by a different instance owner. This error usually occurs because a different host has the instance loaded. The instance owner ID of the owner or host with a lock on the instance is '190ea0d9-788f-4278-883e-84226f5788bc'.

This exception goes away when i stop using the WorkflowApplication.GetInstance method and specifying the version manually, but i would rather avoid that.

So what am i doing wrong here ?

Googling around took me to these pages :

InstanceLockedException: How to handle locking issues with WF 4.0?

-> Looks like the issue i have, but i am not sure how where i can set this timeToUnload value in my current code as i do not use WorkflowServiceHost. Should i just create a new one in each webmethod ? or make a singleton ?

http://social.msdn.microsoft.com/Forums/en-US/3e38a60e-8a99-4c01-a26b-f82670ca5601/instancelockedexception-when-application-restarts-using-persistableidleactionpersist?forum=wfprerelease

-> Tried this on my store with a TimeSpan of 1 sec, did not help either.

Any suggestions would be greatly appreciated :-)

FOLLOW UP

After reading this post : Error when attempting to Resume a Windows Workflow

I have created a DisposableStore class designed as follow :

public class DisposableStore : IDisposable
{
    private const string ConnectionString = "Server=.\\SQLEXPRESS;Initial Catalog=WF45GettingStartedTutorial;Integrated Security=SSPI";
    private SqlWorkflowInstanceStore _store;
    private InstanceHandle _handle;

    public SqlWorkflowInstanceStore Store
    {
        get
        {
            if (_store == null)
            {
                Debug.WriteLine("Building new store");
                _store = new SqlWorkflowInstanceStore(ConnectionString);
                StateMachineStateTracker.Promote(_store);

                _handle = _store.CreateInstanceHandle();
                InstanceView view = _store.Execute(_handle, new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30));
                _store.DefaultInstanceOwner = view.InstanceOwner;
            }

            return _store;
        }
    }

    public void Dispose()
    {
        var deleteOwnerCmd = new DeleteWorkflowOwnerCommand();
        Store.Execute(_handle, deleteOwnerCmd, TimeSpan.FromSeconds(30));
    }
}

And i use it as follow :

[WebMethod]
public bool Resume(Guid instanceId, string bookmarkName, object bookmarkParameter)
{
    Debug.WriteLine(string.Format("Resume - Service id : {0}", _serviceId));
    WorkflowDescriptor descriptor;
    using (var store = new DisposableStore())
    {
        var instance = WorkflowApplication.GetInstance(instanceId, store.Store);
        descriptor = WorkflowLocator.GetWorflowFromIdentity(instance.DefinitionIdentity);
    }

    using (var store = new DisposableStore())
    {
        var wfApp = new WorkflowApplication(descriptor.Activity, descriptor.Identity);
        ConfigureWorkflowApplication(wfApp, store.Store);
        wfApp.Load(instanceId);
        var sync = new AutoResetEvent(false);
        wfApp.Idle = x => sync.Set();
        wfApp.Completed = x=> sync.Set();
        wfApp.ResumeBookmark(bookmarkName, bookmarkParameter);
        sync.WaitOne();
     }

     return true;
}

Things get better with this approach, meaning i no longer get the InstanceLockedException, but now when the Resume method returns to the client, i my wfApp gets aborted :

Exception: System.Runtime.DurableInstancing.InstanceOwnerException The execution of an InstancePersistenceCommand was interrupted because the instance owner registration for owner ID '33ed6492-0685-4f31-8652-4b91acaf50ef' has become invalid. This error indicates that the in-memory copy of all instances locked by this owner have become stale and should be discarded, along with the InstanceHandles. Typically, this error is best handled by restarting the host

Answers


Ok, so i was on the right track using a DisposableStore. The only issue what that the store owner was deleted too soon because of this line :

wfApp.Idle = x => sync.Set();

This was causing my synchroniser to be released too soon, causing the dispose method (which kills the store) to be called before the workflow had a chance to persist itself (which requires the store & its owner).

So i replaced this line with :

wfApp.Unloaded = x => sync.Set();

This way, i wait for the workflow instance to be unloaded before releasing my synchroniser.

So for people facing similar issues, here is the big picture the final code :

[WebMethod]
public bool Resume(Guid instanceId, string bookmarkName, object bookmarkParameter)
{
    Debug.WriteLine(string.Format("Resume - Service id : {0}", _serviceId));
    WorkflowDescriptor descriptor;
    using (var store = new DisposableStore())
    {
        var instance = WorkflowApplication.GetInstance(instanceId, store.Store);
        descriptor = WorkflowLocator.GetWorflowFromIdentity(instance.DefinitionIdentity);
    }

    // We have to create a new store at this point, can't use the same one !!!

    using (var store = new DisposableStore())
    {
        var wfApp = new WorkflowApplication(descriptor.Activity, descriptor.Identity);
        ConfigureWorkflowApplication(wfApp, store.Store);
        wfApp.Load(instanceId);
        var sync = new AutoResetEvent(false);
        wfApp.ResumeBookmark(bookmarkName, bookmarkParameter);
        wfApp.Unloaded = x => sync.Set();
        sync.WaitOne();
    }

    return true;
}

Need Your Help

Camera.Parameters.setRecordingHint and aspect ratio

android android-camera

I've found some odd behavior around Camera.Parameters.setRecordingHint that I'd like to understand better.

parallel programming in C with openmp

c parallel-processing

Right now I am learning about parallel programming in C with openmp and now I have stumbled upon the following problem. I have a simple for loop which I want to parallelize. Using openmp, I thought...