locked
Loading persisted workflow instances with WorkflowApplication RRS feed

  • Question

  • I understand I may be doing this wrong.  I read the topic at http://social.msdn.microsoft.com/Forums/en-CA/wfprerelease/thread/a11eb75d-65de-4787-9745-2fd634d0eee9 but I still don't get it, exactly.

    I have an application that uses WorkflowApplications to handle the workflows.  I have a workflow that contains a one minute Delay activity.  When this activity is hit, the workflow Idles, persists (SqlWorkflowDataStore), and unloads.  This is all fine.  If the app runs for the minute, it will be resumed and will complete.

    However, if I stop the app during the minute that it is delayed, and then start it up again at some point later, the workflows do not resume.  A little digging shows that WorkflowApplication does not resume automatically, and that I have to do that manually.  That's fine.  The problem is, I don't know how.

    I see there is a WorkflowApplication.LoadRunnableInstance, and I see in the db that there are records in the RunnableInstancesTable table.  But when I call LRI, I get back this:

    System.Runtime.DurableInstancing.InstancePersistenceCommandException was unhandled
      Message=In order to match and load a runnable instance in the store, the requesting InstanceOwner must have specified a workflow host type when it was created. To do so, add a key-value pair to the CreateWorkflowOwnerCommand.InstanceOwnerMetadata property bag when issuing the command. The key is the WorkflowHostType key provided in the documentation. The value must be an XName and match that of the instances being loaded.
      Source=System.Runtime.DurableInstancing
      StackTrace:
           at System.Runtime.AsyncResult.End[TAsyncResult](IAsyncResult result)
           at System.Runtime.DurableInstancing.InstancePersistenceContext.OuterExecute(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout)
           at System.Runtime.DurableInstancing.InstanceStore.Execute(InstanceHandle handle, InstancePersistenceCommand command, TimeSpan timeout)
           at System.Activities.WorkflowApplication.PersistenceManager.TryLoad(TimeSpan timeout, IDictionary`2& data)
           at System.Activities.WorkflowApplication.LoadCore(TimeSpan timeout, Boolean loadAny)
           at System.Activities.WorkflowApplication.LoadRunnableInstance(TimeSpan timeout)
           at WF4Sandbox.Program.LoadRunnable(Activity activity) in D:\lab\WF4Sandbox\Program.cs:line 104
           at WF4Sandbox.Program.Main(String[] args) in D:\lab\WF4Sandbox\Program.cs:line 70
           at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
           at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
           at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
           at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
           at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
           at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
           at System.Threading.ThreadHelper.ThreadStart()
      InnerException:

    Which, while verbose, is less than helpful, as there's no documentation on this subject at all.  Digging through the assemblies with Reflector, I can bypass this message by using the following code when creating my instance owner:

                InstanceHandle handle = _store.CreateInstanceHandle(null);
                CreateWorkflowOwnerCommand cmd = new CreateWorkflowOwnerCommand();
                XNamespace hostNamespace = XNamespace.Get("urn:schemas-microsoft-com:System.Activities/4.0/properties");
                XName hostKey = hostNamespace.GetName("WorkflowHostType");
                InstanceValue hostValue = new InstanceValue(XNamespace.Get("http://tempuri.org").GetName("Sentinel"));
                cmd.InstanceOwnerMetadata.Add(hostKey, hostValue);
                InstanceOwner owner = _store.Execute(handle, cmd, TimeSpan.MaxValue).InstanceOwner;
                _store.DefaultInstanceOwner = owner;

    But not only does that not feel right on so many levels, it also doesn't work -- I don't get the error, but neither do any of the instances load.  So my question is, using a WorkflowApplication, how do I resume a delayed workflow?

    Tuesday, April 13, 2010 11:56 PM

All replies

  • Hi Colin,

    The problem could be in the way you use to unload your workflowapplication when the host application is stopped. I think you have your bug because the workflow isn't unload.

    At the applciation eexit you send unload request via Unload method of workflowapplciations?

    http://msdn.microsoft.com/en-us/library/system.activities.workflowapplication.unloaded(v=VS.100).aspx

    If you don't, you have to do it ;).

    That will unload all sql lock on workflow store in database.


    Jérémy Jeanson MCP http://blogs.codes-sources.com/JeremyJeanson/ (French or English Spoken)
    Wednesday, April 14, 2010 7:28 AM
  • Thanks for your answer.  I've set the PersistableIdle to return Unload, and I verify that the workflows still unload.  But still no luck; they never resume.

     

    Looking in the database, I see that the WorkflowHostType column is NULL for all of the workflows.  Looking at TryLoadRunnableInstance, this means that TLRI will never return a match because NULL = NULL => false.

     

    So I figure I'm missing some step in setting up my WorkflowHostType in the code, but I have no idea what that step would be.  Any thoughts?

    Wednesday, April 14, 2010 5:00 PM
  • Hi,

    In the Persisting a Workflow Application sample there is the following code when they setup the instance store:

    private static void SetupInstanceStore(){ instanceStore =   new SqlWorkflowInstanceStore(@"Data Source=.\SQLEXPRESS;Initial Catalog=SampleInstanceStore;Integrated Security=True;Asynchronous Processing=True");    InstanceHandle handle = instanceStore.CreateInstanceHandle();    InstanceView view = instanceStore.Execute(handle, new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30));    handle.Free(); instanceStore.DefaultInstanceOwner = view.InstanceOwner;}

    This sample doesn't have a delay activity and so it doesn't illustrate your scenario, but this is an example of how to setup the workflow owner and might be the missing piece in your case. [Edit - I am running this sample and it does not register the host type, I will look at it some more and provide an update shortly)

    Steve Danielson [Microsoft]
    This posting is provided "AS IS" with no warranties, and confers no rights.
    Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm

     

    Thursday, April 15, 2010 9:07 PM
  • There is a new sample is the WF/WCF samples package that shows how to setup the workflow owner:

    http://www.microsoft.com/downloads/details.aspx?FamilyID=35ec8682-d5fd-4bc3-a51a-d8ad115a8792&displaylang=en

     

    Once you extract the samples the path is this:  WF_WCF_Samples\WF\Basic\Services\AbsoluteDelay\CS\AbsoluteDelay

    The documentation for this sample has not been prepared so I don't have a link for that, but it shows how to setup the owner, and it also has a sample custom delay activity that manages some of the resuming and so forth, but it also has a regular delay activity. It configures the owner like this:

    // Configure a Default Owner for the instance store so instances can be re-loaded from WorkflowApplicationprivate static InstanceHandle CreateInstanceStoreOwner(InstanceStore store, XName wfHostTypeName){ InstanceHandle ownerHandle = store.CreateInstanceHandle(); CreateWorkflowOwnerCommand ownerCommand = new CreateWorkflowOwnerCommand() {  InstanceOwnerMetadata =  {   { WorkflowHostTypePropertyName, new InstanceValue(wfHostTypeName) }  } }; store.DefaultInstanceOwner = store.Execute(ownerHandle, ownerCommand, TimeSpan.FromSeconds(30)).InstanceOwner; return ownerHandle;}

    Apologies if this sample code snippet runs all into one line, the last few I have pasted in seem to do that and I am unsure why... [Edit, it did, trying a different way...]

    Steve Danielson [Microsoft]
    This posting is provided "AS IS" with no warranties, and confers no rights.
    Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm

     

    Thursday, April 15, 2010 10:35 PM
  • Making progress.  I now see that the WorkflowHostType is set correctly in the database.

    I can resume workflows that have been unloaded.

    However, if I start some workflows, exit the application while the workflows are in a delay state, and then restart the application, it never seems to get signaled that there are workflows waiting to be resumed from the database.  Calls to WaitForEvents block and timeout.

    I see the same behavior in the AbsoluteDelay sample -- I start a workflow, close the application during the delay, and then restart the application (skipping the creation of the workflow and going straight to the "WaitForRunnableInstance" method), it also blocks and never seems to receive notice that there are workflows to be resumed.

     

    RunnableInstancesTable has the instances in it; if I send a TryLoadRunnableWorkflowCommand to the instance store and watch in the profiler, I see that it does indeed find and return an instance to run....but WaitForEvents continues to just block forever.

     

    This is a cut down version of the code that I'm using, that still replicates the problem: I have dozens of instances in the RunnableInstancesTable with the appropriate WorkflowHostType, but none of them load when I run this code:

     

    using System;
    using System.Activities;
    using System.Activities.DurableInstancing;
    using System.Activities.XamlIntegration;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Runtime.DurableInstancing;
    using System.Text;
    using System.Threading;
    using System.Xml.Linq;

    namespace WF4Sandbox
    {
        class Program
        {
            private static int _waitingCount = 0;
            private static int _finished = 0;
            private static int _running = 0;
            private static readonly object _syncRoot = new object();
            private static readonly InstanceStore _store = new SqlWorkflowInstanceStore("Server=localhost;Database=SampleInstanceStore;Integrated Security=true;");
            private static InstanceOwner _owner;

            // A well known property that is needed by WorkflowApplication and the InstanceStore
            private static readonly XName WorkflowHostTypePropertyName = XNamespace.Get("urn:schemas-microsoft-com:System.Activities/4.0/properties").GetName("WorkflowHostType");
            private static readonly XName wfHostTypeName = XName.Get("Sandbox", "WINS");

            static void Main(string[] args)
            {
                string XAML = string.Join("\n", File.ReadAllLines(@"D:\lab\WF4Sandbox\TestWorkflow.xaml"));
                InstanceHandle handle = _store.CreateInstanceHandle(null);
                CreateWorkflowOwnerCommand cmd = new CreateWorkflowOwnerCommand()
                 {
                     InstanceOwnerMetadata =
                         {
                             {WorkflowHostTypePropertyName, new InstanceValue(wfHostTypeName)}
                         }
                 };
                _owner = _store.Execute(handle, cmd, TimeSpan.MaxValue).InstanceOwner;
                _store.DefaultInstanceOwner = _owner;
                handle.Free();
               
                Activity activity;
                using (StringReader rdr = new StringReader(XAML))
                {
                    activity = ActivityXamlServices.Load(rdr);
                }

                while (true)
                {
                    List<InstancePersistenceEvent> events;
                    try
                    {
                        InstanceHandle handle2 = _store.CreateInstanceHandle(_owner);
                        events = _store.WaitForEvents(handle2, new TimeSpan(0, 0, 10));
                        handle2.Free();
                    }
                    catch (TimeoutException)
                    {
                        events = new List<InstancePersistenceEvent>();
                        Console.WriteLine("Timed out");
                    }
                    if (events.Count > 0)
                    {
                        foreach (InstancePersistenceEvent evt in events)
                        {
                            if (evt == HasRunnableWorkflowEvent.Value)
                            {
                                Console.WriteLine("FOUND RUNNABLE!");
                                WorkflowApplication app2 = LoadRunnable(activity);
                                app2.Run();
                                break;
                            } else if (evt == HasActivatableWorkflowEvent.Value)
                            {
                                Console.WriteLine("FOUND ACTIVATABLE!");
                            }
                        }
                    }
                    Console.WriteLine(string.Format("Waiting...{0}: # of running workflows: {1}", ++_waitingCount, _running));
                    Thread.Sleep(1000);
                }
            }

            static WorkflowApplication LoadRunnable(Activity activity)
            {
                WorkflowApplication app = new WorkflowApplication(activity);
                SetupApplication(app);
                try
                {
                    app.LoadRunnableInstance(new TimeSpan(0, 0, 10));
                } catch (TimeoutException)
                {
                    return null;
                } catch (InstanceNotReadyException)
                {
                    return null;
                }
                Console.WriteLine(string.Format("Loading runnable workflow {0}: # of runnable workflows: {1}", app.Id, _running));
                return app;
            }

            static void SetupApplication(WorkflowApplication app)
            {
                app.InstanceStore = _store;
                app.Completed = e =>
                {
                    lock (_syncRoot)
                    {
                        _running--;
                        Console.WriteLine(string.Format("EVENT: {0}: Completed: # of runnable workflows: {1}", e.InstanceId, _running));
                    }
                };
                Dictionary<XName, object> wfScope = new Dictionary<XName, object>  { { WorkflowHostTypePropertyName, wfHostTypeName } };
                app.AddInitialInstanceValues(wfScope);
                app.PersistableIdle = e => {
                       return PersistableIdleAction.Unload;
                };
                app.Idle = e =>
                {
                    Console.WriteLine(string.Format("EVENT: {0}: Idled: # of runnable workflows: {1}", e.InstanceId, _running));
                };
                app.Unloaded = e =>
                   {
                    Console.WriteLine(string.Format("EVENT: {0}: Unloaded: # of runnable workflows: {1}", e.InstanceId, _running));
                };
            }
        }
    }

    • Proposed as answer by BayerWhite Sunday, April 18, 2010 3:17 AM
    Friday, April 16, 2010 10:37 PM
  • Colin,

    You can simplify what you are trying to do by changing how you reload a workflow instance. Instead try the following using the WorkflowApplication...

    WorkflowApplication.Load("specific GUID for the WF instance")

    WorkflowApplication.Run()

     


    Blogging at Humanworkflow.net
    Sunday, April 18, 2010 3:33 AM
  • So I should not be using WorkflowApplication.LoadRunnableInstance, but should instead do the polling of the database on my own?

     

    That's an acceptable answer; I would just hope that that sort of information would've been in the documentation for LoadRunnableInstance, if it's true.

    Monday, April 19, 2010 4:42 PM
  • Colin,

    The WF Team has done significant improvements with persisting WF instances to the point that it really just depends on what you are trying to do. I was really just trying to get you kick started for getting your delay activity to respond to a reloaded WF instance from the persistence store. Once I get more clarification I will add it to the thread. 


    Blogging at Humanworkflow.net
    Tuesday, April 20, 2010 4:33 PM
  • I see the same behavior in the AbsoluteDelay sample -- I start a workflow, close the application during the delay, and then restart the application (skipping the creation of the workflow and going straight to the "WaitForRunnableInstance" method), it also blocks and never seems to receive notice that there are workflows to be resumed.


    Let me look into this; my understanding of the sample is that it should resume those delays even after a host shutdown.

    Edit: the sample doesn't reload them after a restart because it uses a unique name each time:

    // Create a unique name that is used to associate instances in the instance store hosts that can load them. This is needed to prevent a Workflow host from loading
    // instances that have different implementations. The unique name should change whenever the implementation of the workflow changes to prevent workflow load exceptions.
    // For the purposes of the demo we create a unique name every time the program is run.
    XName wfHostTypeName = XName.Get("Version" + Guid.NewGuid().ToString(), typeof(Workflow1).FullName);

    If you take the guid out it does. However, I didn't see a guid in your sample code, it looks like you are using a fixed name across host shutdowns. I'll keep looking into it some more.

    Steve Danielson [Microsoft]
    This posting is provided "AS IS" with no warranties, and confers no rights.
    Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm

     

    Tuesday, April 20, 2010 8:28 PM
  • Yes.. I experienced as well that LoadRunnableInstance does not work properly.

    It seems the best approach to achieve this with WorkflowApplication at this point is:
    1. to create a service and schedule it to fire every minute or so.
    2. look into the Instance Table and obtain all the Guids with Pending Timer column not null
    3. Then invoke the following in that scheduled service:

     foreach(Guid g in GuidList)
     {
       app.Load( g );
       app.Run();
     }

    If you do this, delayed activities will execute if the Pending Timer is due to execute.  
    Wish it was known why Microsoft decided to leave WorkflowApplication like this.. At least LoadRunnableInstance needs to work properly.

    • Proposed as answer by Secret To Life Monday, October 18, 2010 6:04 PM
    Friday, October 8, 2010 8:55 PM
  • As mentioned above the sample "\WF_WCF_Samples\WF\Basic\Services\AbsoluteDelay" has the solution where there are 2 changes you need to make (in Program.cs):

    // Original
    XName wfHostTypeName = XName.Get("Version" + new Guid().ToString(), typeof(Workflow1).FullName);

    //Change to..
    XName wfHostTypeName = XName.Get("Version" + "{your fixed version}", typeof(Workflow1).FullName);


    //Original
     if (workflowCompleted)
            {
              break;
            }

    //Change to:
     if (workflowCompleted)
            {
              //break;
            }

    • Edited by Secret To Life Tuesday, October 19, 2010 12:41 PM code font too small
    • Proposed as answer by Hsakarp_Raj Monday, June 11, 2012 9:16 AM
    • Unproposed as answer by Hsakarp_Raj Monday, June 11, 2012 9:16 AM
    Monday, October 18, 2010 6:11 PM