Click here to Skip to main content
15,887,267 members
Articles / Programming Languages / C#

Long Running Work Flow Activities

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
11 Dec 2008CPOL18 min read 45.1K   385   36  
A generic way to write long running work flow activities
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Workflow.Runtime;
using System.Threading;
using CommonWFLibrary;
using System.IO;

namespace CommonWFLibraryTest
{
    /// <summary>
    /// Summary description for t_ExternalProgramActivity
    /// </summary>
    [TestClass]
    public class t_ExternalProgramActivity
    {
        public t_ExternalProgramActivity()
        {
            //
            // TODO: Add constructor logic here
            //
        }

        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        /// <summary>
        /// Run a test, throw exceptions back to user.
        /// </summary>
        /// <param name="wfActivity"></param>
        /// <param name="dict"></param>
        private static Dictionary<string, object> RunWFByType(Type wfActivity, Dictionary<string, object> dict)
        {
            using (WorkflowRuntime runtime = new WorkflowRuntime())
            using (AutoResetEvent reset = new AutoResetEvent(false))
            {
                Exception exp = null;
                Dictionary<string, object> results = null;
                runtime.WorkflowCompleted += (o, args) => { results = args.OutputParameters; reset.Set(); };
                runtime.WorkflowTerminated += (o, args) => { exp = args.Exception; reset.Set(); };

                LongRunningActivityBase.RegisterService(runtime);
                runtime.StartRuntime();

                WorkflowInstance instance;
                instance = runtime.CreateWorkflow(wfActivity, dict);
                instance.Start();
                bool result = reset.WaitOne(15*1000);

                if (exp != null)
                {
                    throw exp;
                }

                return results;
            }
        }

        /// <summary>
        /// Run a test, throw exceptions back to user. Only, once we go into an idle state, then
        /// crash the WF and restart it. And hook up all the required persistancy!
        /// </summary>
        /// <param name="wfActivity"></param>
        /// <param name="dict"></param>
        /// <returns>A list of the WF public properties upon finish up!</returns>
        private static Dictionary<string,object> RunWFByTypeWithPersistance(Type wfActivity, Dictionary<string, object> dict, int timeout, DirectoryInfo cache, int numberCrashToIdles, int ticksToPauseInbetween)
        {
            ///
            /// Crash the runtime n times.
            /// 

            bool start_wf = true;
            bool has_entered_idle = false;
            Dictionary<string, object> results = null;
            while (numberCrashToIdles >= 0)
            {
                numberCrashToIdles = numberCrashToIdles - 1;
                bool waitForComplete = numberCrashToIdles < 0;

                ///
                /// Run until the WF enters the idle state. If it crashes, then, of course, we are outta here.
                /// 

                using (AutoResetEvent reset = new AutoResetEvent(false))
                using (WorkflowRuntime runtime = new WorkflowRuntime())
                {
                    try
                    {
                        Exception e = null;
                        bool completed = false;
                        if (!waitForComplete)
                        {
                            runtime.WorkflowIdled += (o, args) => { reset.Set(); has_entered_idle = true; };
                        }
                        runtime.WorkflowCompleted += (o, args) => { completed = true; results = args.OutputParameters; reset.Set(); };
                        runtime.WorkflowTerminated += (o, args) => { e = args.Exception; reset.Set(); };

                        runtime.AddService(new FilePersistenceService(true, cache));
                        LongRunningActivityBinaryPersister lrp = new LongRunningActivityBinaryPersister(new FileInfo(cache.FullName + "\\longrunning"));
                        LongRunningActivityBase.RegisterService(runtime, obj => lrp.Save(obj), () => lrp.Restore());
                        runtime.StartRuntime();

                        ///
                        /// If this is the first time through, then we should start the WF. Otherwise, it
                        /// should have been picked up by the persitancy service from the last run.
                        /// 

                        if (start_wf)
                        {
                            WorkflowInstance instance;
                            instance = runtime.CreateWorkflow(wfActivity, dict);
                            instance.Start();
                            start_wf = false;
                        }

                        ///
                        /// Great. Now wait for it to finish. We expect the WF to enter
                        /// an idled state within 1 second. On the last one, waiting for something
                        /// real to happen, we expect that to happen in the timeout parameter passed
                        /// to us.
                        /// 
                        /// We shut down the runtime here so that we can step through the other statements in the
                        /// debugger!
                        /// 
                        /// We wait for 100 ms after the idle guy goes because we have to make sure
                        /// that the service has time to close and save state. In real life, there is
                        /// a potential race condition - that is, if the wf crashes before our system has a chance
                        /// to write out its state then the long running WF will, indeed, be left hanging. But that
                        /// is proper behavior. Just not for a test harness.
                        /// 

                        int waittime = waitForComplete ? timeout : 1000;
                        bool result = reset.WaitOne(waittime);
                        Thread.Sleep(100);
                        runtime.StopRuntime();
                        runtime.Dispose();

                        ///
                        /// First, if the WF ended due to an exception, we are outta here no matter what
                        /// 

                        if (e != null)
                        {
                            throw e;
                        }

                        ///
                        /// Next, if we are waiting for a complete, then we just return the result
                        /// 

                        if (waitForComplete)
                        {
                            return results;
                        }

                        ///
                        /// Ok -- we were waiting for things to go into the idle state. Bomb if they didn't,
                        /// otherwise we go around again!
                        /// 

                        if (completed)
                        {
                            throw new Exception("The WF unexpectedly completed before going idle!");
                        }
                        if (!result && !has_entered_idle)
                        {
                            throw new Exception("The workflow was never idled");
                        }

                        ///
                        /// Pause before the next iteration if requested...
                        /// 

                        if (ticksToPauseInbetween > 0)
                        {
                            Thread.Sleep(ticksToPauseInbetween);
                        }
                    }
                    finally
                    {
                        if (runtime.IsStarted)
                        {
                            runtime.StopRuntime();
                        }
                    }
                }
            }
            return null;
        }


        [TestMethod]
        public void TestSimpleRun()
        {
            /// Run the external program
            Assert.IsTrue(null != RunWFByType(typeof(ExternalProgramActivity), new Dictionary<string, object> { { "ProgramPath", "RunAndPauseWithArg.exe" } }), "Failed to get program return!");
        }

        [TestMethod]
        public void TestSimpleRunBadPath()
        {
            try
            {
                /// Make sure the exception comes back when we can't find the program we are trying to run!
                RunWFByType(typeof(ExternalProgramActivity), new Dictionary<string, object> { { "ProgramPath", "RunAndPauseWithArgFreak.exe" } });
                Assert.Fail("Should have thrown exception because the path of the program we are trying to run does not exist!");
            }
            catch (Exception)
            {
            }
        }

        [TestMethod]
        public void TestRunArgs()
        {
            /// Run external guys with argument string - make sure it gets there!
            DateTime start = DateTime.Now;

            RunWFByType(typeof(ExternalProgramActivity),
                new Dictionary<string, object> {
                { "ProgramPath", "RunAndPauseWithArg.exe" },
                { "ProgramArguments", "3000" }
                });

            DateTime finish = DateTime.Now;
            TimeSpan delta = finish - start;
            Assert.IsTrue(delta.Seconds >= 3, "The wait was supposed to be more than 3 seconds, but was not!");
            Assert.IsTrue(delta.Seconds <= 4, "Should have waited less than 4 seconds!");
        }

        [TestMethod]
        public void TestRunResult()
        {
            /// Run external program, make sure its result is saved
            var results = RunWFByType(typeof(ExternalProgramActivity),
                new Dictionary<string, object> {
                { "ProgramPath", "RunAndPauseWithArg.exe" },
                { "ProgramArguments", "50 5" }
                });

            Assert.IsTrue(results.ContainsKey("FinalStatus"), "The final status property did not come back!");
            Assert.IsTrue((int) results["FinalStatus"] == 5, "Final status was not 5!");
        }

        [TestMethod]
        public void TestRunHostCrashWhileProgramRunning()
        {
            DateTime start = DateTime.Now;
            var results = RunWFByTypeWithPersistance(
                typeof(ExternalProgramActivity),
                new Dictionary<string, object> { { "ProgramPath", "RunAndPauseWithArg.exe" }, { "ProgramArguments", "5000" } },
                6000,
                new DirectoryInfo(".\\TestRunHostCrashWhileProgramRunning"),
                1,
                2000);
            DateTime finish = DateTime.Now;
            TimeSpan delta = finish - start;

            Assert.IsTrue(results != null, "The WF failed to complete!");
            Assert.IsTrue(delta.Seconds >= 5, "The WF should have taken at least 5 seconds -- but took less!?");
            Assert.IsTrue(delta.Seconds <= 6, "The WF shoudl not have taken more than 6 seconds -- something is wrong!");
        }

        [TestMethod]
        public void TestRunHostCrashNoRestart()
        {
            ///
            /// Crash the host, but set the "restart" value to false (normally it is true).
            /// 

            try
            {
                var results = RunWFByTypeWithPersistance(
                    typeof(ExternalProgramActivity),
                    new Dictionary<string, object> { { "ProgramPath", "RunAndPauseWithArg.exe" }, { "ProgramArguments", "500" }, { "AllowRestart", false } },
                    2000,
                    new DirectoryInfo(".\\TestRunHostCrashWhileProgramRunning"),
                    1,
                    2000);
                Assert.Fail("Run should have thrown an exception that we couldn't restart the program");
            }
            catch (AssertFailedException e)
            {
                throw e;
            }
            catch (Exception e)
            {
                Assert.IsTrue(e.InnerException.GetType() == typeof(ExternalProgramActivityException), "Incorrect exception type came back");
                Assert.IsTrue(e.InnerException.Message.Contains("Program crashed"), "Exception was not correct");
            }
        }

        [TestMethod]
        public void TestRunHostCrashRestart()
        {
            /// Have the program crash while the WF host is down. So it should be re-run.
            var results = RunWFByTypeWithPersistance(
                typeof(ExternalProgramActivity),
                new Dictionary<string, object> { { "ProgramPath", "RunAndPauseWithArg.exe" }, { "ProgramArguments", "500" } },
                6000,
                new DirectoryInfo(".\\TestRunHostCrashWhileProgramRunning"),
                1,
                2000);

            Assert.IsTrue(results != null, "The WF failed to complete!");
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Other University of Washington
United States United States
I'm a professor of physics at the University of Washington - my field of research is particle physics. I went into this because of the intersection of physics, hardware, and computers. I've written large experiment data aquisition systems (I've done a lot of multi-thread programming). My hobby is writing tools and other things that tend to be off-shoots of work-related projects.

Comments and Discussions