Aug 18 2010

Getting meaningful exceptions from WF

Category: .NetRory Primrose @ 08:33

Overall I love the changes made to WF4. The latest workflow support is a huge leap forward from the 3.x versions. Unfortunately both versions suffer the same issues regarding feedback for the developer when exceptions are thrown.

Consider the following simple workflow example.

image

namespace WorkflowConsoleApplication1
{
    using System;
    using System.Activities;

    class Program
    {
        static void Main(string[] args)
        { 
            try
            {
                WorkflowInvoker.Invoke(new Workflow1());
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            Console.ReadKey();
        }
    }
}

This example provides the following output.

System.InvalidOperationException: Something went wrong
   at System.Activities.WorkflowApplication.Invoke(Activity activity, IDictionar
y`2 inputs, WorkflowInstanceExtensionManager extensions, TimeSpan timeout)
   at System.Activities.WorkflowInvoker.Invoke(Activity workflow, TimeSpan timeo
ut, WorkflowInstanceExtensionManager extensions)
   at System.Activities.WorkflowInvoker.Invoke(Activity workflow)
   at WorkflowConsoleApplication1.Program.Main(String[] args) in e:\users\profil
edr\documents\visual studio 2010\Projects\WorkflowConsoleApplication2\WorkflowCo
nsoleApplication2\Program.cs:line 12

Exceptions thrown from the workflow engine identify the exception type and message but do not contain an accurate stack trace. Exceptions are caught by the workflow engine and re-thrown when WorkflowInvoker.Invoke is used. This means that the stack trace identifies where the exception has been re-thrown rather than where the exception was originally raised.

Debugging WF activities with this constraint is very difficult. Trying to debug your workflows gets exponentially harder with increasing complexity of the workflow system and it's underlying components. This is compounded when the exception (such as a NullReferenceException) doesn't provide enough information by itself to pinpoint where the exception was thrown.

The solution to this is in two parts. The first improvement is to provide more detail in the stack trace and the second is to identify the activity hierarchy involved.

Enhancing the stack trace

Using the throw statement on an existing exception instance wipes out any existing stack trace information. This is why you should never catch ex then throw ex, you should always just use the throw statement by itself. This isn’t possible in the case of the workflow engine as it has caught the exception on a different thread and has no choice but to throw the exception instance back on the original calling thread.

The trick here is to get the exception to preserve the original stack trace when it is re-thrown. Exception has a method called InternalPreserveStackTrace that will do this for us. Reflection must be used here because the method is marked as internal.

The first thing to change is that we need to use WorkflowApplication directly rather than WorkflowInvoker. WorkflowInvoker is great for simplicity but does not provide direct access to the thrown exception before it is re-thrown. Using WorkflowApplication provides the ability to hook the OnUnhandledException action where the exception is available. The reflected InternalPreserveStackTrace method can then be invoked on the exception instance before it is re-thrown.

namespace WorkflowConsoleApplication1
{
    using System;
    using System.Activities;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Text;
    using System.Threading;

    internal class ActivityInvoker
    {
        private static readonly MethodInfo _preserveStackTraceMethod = GetExceptionPreserveMethod();

        public static IDictionary<String, Object> Invoke(Activity activity, IDictionary<String, Object> inputParameters = null)
        {
            if (inputParameters == null)
            {
                inputParameters = new Dictionary<String, Object>();
            }

            WorkflowApplication application = new WorkflowApplication(activity, inputParameters);
            Exception thrownException = null;
            IDictionary<String, Object> outputParameters = new Dictionary<String, Object>();
            ManualResetEvent waitHandle = new ManualResetEvent(false);

            application.OnUnhandledException = (WorkflowApplicationUnhandledExceptionEventArgs arg) =>
            {
                // Preserve the stack trace in this exception
                // This is a hack into the Exception.InternalPreserveStackTrace method that allows us to re-throw and preserve the call stack
                _preserveStackTraceMethod.Invoke(arg.UnhandledException, null);

                thrownException = arg.UnhandledException;

                return UnhandledExceptionAction.Terminate;
            };
            application.Completed = (WorkflowApplicationCompletedEventArgs obj) =>
            {
                waitHandle.Set();

                outputParameters = obj.Outputs;
            };
            application.Aborted = (WorkflowApplicationAbortedEventArgs obj) => waitHandle.Set();
            application.Idle = (WorkflowApplicationIdleEventArgs obj) => waitHandle.Set();
            application.PersistableIdle = (WorkflowApplicationIdleEventArgs arg) =>
            {
                waitHandle.Set();

                return PersistableIdleAction.Persist;
            };
            application.Unloaded = (WorkflowApplicationEventArgs obj) => waitHandle.Set();

            application.Run();

            waitHandle.WaitOne();

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

            return outputParameters;
        }

        private static MethodInfo GetExceptionPreserveMethod()
        {
            return typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic);
        }
    }
}

Changing the example program code to use this ActivityInvoker class rather than WorkflowInvoker now provides some more detailed stack trace information.

System.InvalidOperationException: Something went wrong
   at System.Activities.Statements.Throw.Execute(CodeActivityContext context)
   at System.Activities.CodeActivity.InternalExecute(ActivityInstance instance,
ActivityExecutor executor, BookmarkManager bookmarkManager)
   at System.Activities.ActivityInstance.Execute(ActivityExecutor executor, Book
markManager bookmarkManager)
   at System.Activities.Runtime.ActivityExecutor.ExecuteActivityWorkItem.Execute
Body(ActivityExecutor executor, BookmarkManager bookmarkManager, Location result
Location)
   at WorkflowConsoleApplication1.ActivityInvoker.Invoke(Activity activity, IDic
tionary`2 inputParameters) in e:\users\profiledr\documents\visual studio 2010\Pr
ojects\WorkflowConsoleApplication2\WorkflowConsoleApplication2\ActivityInvoker.c
s:line 82
   at WorkflowConsoleApplication1.Program.Main(String[] args) in e:\users\profil
edr\documents\visual studio 2010\Projects\WorkflowConsoleApplication2\WorkflowCo
nsoleApplication2\Program.cs:line 11

The stack trace with preservation now includes the stack frames between ActivityInvoker.Invoke and the class that originally threw the exception. This is particularly helpful when the exception was thrown in an underlying component. Unfortunately the architecture of WF does not often provide much of a stack trace in itself. Preserving the stack trace in this simple scenario has added helpful information but not enough to make it really easy to debug.

Identifying the activity hierarchy

Identifying the hierarchy of activities being executed will add further debugging assistance. The Activity class in the 3.x version of WF contained a public Parent property. You could recursively traverse up the chain of parent activities to create a hierarchy of activities to provide this information. This property is still there in version 4.0 but is now internal. Using reflection is again the only option for obtaining this information.

A tweak to the exception logic in the ActivityInvoker class will hook into the Activity.Parent property to calculate the activity hierarchy.

namespace WorkflowConsoleApplication1
{
    using System;
    using System.Activities;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Text;
    using System.Threading;

    internal class ActivityInvoker
    {
        private static readonly PropertyInfo _parentProperty = GetActivityParentProperty();

        private static readonly MethodInfo _preserveStackTraceMethod = GetExceptionPreserveMethod();

        public static Exception CreateActivityFailureException(Exception exception, Activity source)
        {
            // Create the hierarchy of activity names
            Activity activity = source;
            StringBuilder builder = new StringBuilder(exception.Message);
            builder.AppendLine();
            builder.AppendLine();

            builder.AppendLine("Workflow exception throw from the following activity stack:");

            while (activity != null)
            {
                builder.AppendLine(activity + " - " + activity.GetType().FullName);

                activity = _parentProperty.GetValue(activity, null) as Activity;
            }

            return new ActivityFailureException(builder.ToString(), exception);
        }

        public static IDictionary<String, Object> Invoke(Activity activity, IDictionary<String, Object> inputParameters = null)
        {
            if (inputParameters == null)
            {
                inputParameters = new Dictionary<String, Object>();
            }

            WorkflowApplication application = new WorkflowApplication(activity, inputParameters);
            Exception thrownException = null;
            IDictionary<String, Object> outputParameters = new Dictionary<String, Object>();
            ManualResetEvent waitHandle = new ManualResetEvent(false);

            application.OnUnhandledException = (WorkflowApplicationUnhandledExceptionEventArgs arg) =>
            {
                // Preserve the stack trace in this exception
                // This is a hack into the Exception.InternalPreserveStackTrace method that allows us to re-throw and preserve the call stack
                _preserveStackTraceMethod.Invoke(arg.UnhandledException, null);

                thrownException = CreateActivityFailureException(arg.UnhandledException, arg.ExceptionSource);

                return UnhandledExceptionAction.Terminate;
            };
            application.Completed = (WorkflowApplicationCompletedEventArgs obj) =>
            {
                waitHandle.Set();

                outputParameters = obj.Outputs;
            };
            application.Aborted = (WorkflowApplicationAbortedEventArgs obj) => waitHandle.Set();
            application.Idle = (WorkflowApplicationIdleEventArgs obj) => waitHandle.Set();
            application.PersistableIdle = (WorkflowApplicationIdleEventArgs arg) =>
            {
                waitHandle.Set();

                return PersistableIdleAction.Persist;
            };
            application.Unloaded = (WorkflowApplicationEventArgs obj) => waitHandle.Set();

            application.Run();

            waitHandle.WaitOne();

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

            return outputParameters;
        }

        private static PropertyInfo GetActivityParentProperty()
        {
            return typeof(Activity).GetProperty("Parent", BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic);
        }

        private static MethodInfo GetExceptionPreserveMethod()
        {
            return typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic);
        }
    }
}

The change here calculates the activity hierarchy and appends it to the exception message. A custom ActivityFailureException is used to help identify that this an exception handled from the workflow engine. The original exception is still available via the InnerException property.

The output of the program now provides the extended stack trace and the activity hierarchy.

WorkflowConsoleApplication1.ActivityFailureException: Something went wrong

Workflow exception thrown from the following activity stack:
1.5: Throw - System.Activities.Statements.Throw
1.4: Fourth Sequence - System.Activities.Statements.Sequence
1.3: Third Sequence - System.Activities.Statements.Sequence
1.2: Second Sequence - System.Activities.Statements.Sequence
1.1: First Sequence - System.Activities.Statements.Sequence
1: Workflow1 - WorkflowConsoleApplication1.Workflow1
 ---> System.InvalidOperationException: Something went wrong
   at System.Activities.Statements.Throw.Execute(CodeActivityContext context)
   at System.Activities.CodeActivity.InternalExecute(ActivityInstance instance,
ActivityExecutor executor, BookmarkManager bookmarkManager)
   at System.Activities.ActivityInstance.Execute(ActivityExecutor executor, Book
markManager bookmarkManager)
   at System.Activities.Runtime.ActivityExecutor.ExecuteActivityWorkItem.Execute
Body(ActivityExecutor executor, BookmarkManager bookmarkManager, Location result
Location)

   --- End of inner exception stack trace ---
   at WorkflowConsoleApplication1.ActivityInvoker.Invoke(Activity activity, IDic
tionary`2 inputParameters) in e:\users\profiledr\documents\visual studio 2010\Pr
ojects\WorkflowConsoleApplication2\WorkflowConsoleApplication2\ActivityInvoker.c
s:line 80
   at WorkflowConsoleApplication1.Program.Main(String[] args) in e:\users\profil
edr\documents\visual studio 2010\Projects\WorkflowConsoleApplication2\WorkflowCo
nsoleApplication2\Program.cs:line 11

The combination of these two pieces of information will now allow you to quickly identify where the exception is being thrown in or through your workflow assembly.

Tags:

Aug 6 2010

Developing multi-threaded workflows

Category: .NetRory Primrose @ 07:21

Most people (myself included) assume that the Parallel and ParallelForEach<T> activities in WF4 run each child in parallel on multiple threads. Unfortunately this is not the case. Each child activity is scheduled in the workflow runtime at the same time. The child activities will only start running in “parallel” if one of the branches is in a waiting state. You can read this post which links to this post for some more detailed information

You can achieve multi-threaded parallel execution by using AsyncCodeActivity derived activities (such as InvokeMethod) with the RunAsynchronously set to True running in a Parallel or ParallelForEach<T> activity. Consider the following workflow.

image

Each parallel branch writes the start time to the console, runs a Thread.Sleep for 2, 4 or 6 seconds and writes the end time. The following is written to the console if each InvokeMethod activity has RunAsynchronously set to False.

Starting - 6/08/2010 10:41:38 AM
Starting First - 6/08/2010 10:41:38 AM
Finishing First - 6/08/2010 10:41:40 AM
Starting Second - 6/08/2010 10:41:41 AM
Finishing Second - 6/08/2010 10:41:45 AM
Starting Third - 6/08/2010 10:41:45 AM
Finishing Third - 6/08/2010 10:41:51 AM
Completed - 6/08/2010 10:41:51 AM

Each branch in the parallel is waiting until the previous branch has completed. The following is written to the console if each InvokeMethod activity has RunAsynchronously set to True.

Starting - 6/08/2010 10:47:35 AM
Starting First - 6/08/2010 10:47:35 AM
Starting Second - 6/08/2010 10:47:35 AM
Starting Third - 6/08/2010 10:47:35 AM
Finishing First - 6/08/2010 10:47:37 AM
Finishing Second - 6/08/2010 10:47:39 AM
Finishing Third - 6/08/2010 10:47:41 AM
Completed - 6/08/2010 10:47:41 AM

Each parallel branch has now executed on different threads. The overall affect is that the workflow now runs must faster.

ParallelForEach<T> works in the same manner. This workflow can be refactored to the following:

image

The result without asynchronous processing is:

Starting - 6/08/2010 11:55:09 AM
Starting 1 - 6/08/2010 11:55:09 AM
Finishing 1 - 6/08/2010 11:55:11 AM
Starting 2 - 6/08/2010 11:55:11 AM
Finishing 2 - 6/08/2010 11:55:16 AM
Starting 3 - 6/08/2010 11:55:16 AM
Finishing 3 - 6/08/2010 11:55:22 AM
Completed - 6/08/2010 11:55:22 AM

The result with asynchronous processing is:

Starting - 6/08/2010 11:56:02 AM
Starting 1 - 6/08/2010 11:56:02 AM
Starting 2 - 6/08/2010 11:56:02 AM
Starting 3 - 6/08/2010 11:56:02 AM
Finishing 1 - 6/08/2010 11:56:04 AM
Finishing 2 - 6/08/2010 11:56:07 AM
Finishing 3 - 6/08/2010 11:56:08 AM
Completed - 6/08/2010 11:56:08 AM

What happens when an exception is thrown? If the exception is thrown in the parallel branch, it will stop executing all the other running branches and the exception will be thrown up the call stack. Any exceptions thrown from within the asynchronous code will have the exception pushed back onto the original workflow execution thread and then thrown. Effectively there is no difference regarding where the exception is thrown from, the parallel branches will be stopped.

Tags:

Aug 2 2010

Cleaning a VS2010 solution with a sledgehammer

Category: .Net | My SoftwareRory Primrose @ 07:11

My workplace has been having issues with VS2010 picking up old assemblies since we have been using VS2010 on a complex services solution. The issue usually pops up when executing test runs with the built in support for MSTest. This occasionally happened in VS2008 but is much more prevalent in 2010. The scenario seems to be that somewhere between the IDE and MSTest is picking up assemblies from prior TestResults directories if the assembly can’t be found in bin\Debug or bin\Release directories. This means that the normal clean operation under the build menu is not sufficient to get rid of this problem.

Enter the sledgehammer. I wrote a little utility that will recursively go through each bin, obj and TestResults folder under the solution path and delete everything it can (including read-only files). Any exceptions encountered will be output to the console. The code itself is really simple.

namespace Neovolve.SolutionCleaner
{
    using System;
    using System.IO;
 
    internal class Program
    {
        private static void DeleteDirectory(String directoryPath)
        {
            String[] directories = Directory.GetDirectories(directoryPath);

            for (int index = 0; index < directories.Length; index++)
            {
                String childDirectory = directories[index];

                DeleteDirectory(childDirectory);
            }

            Console.WriteLine("Cleaning directory " + directoryPath);

            String[] files = Directory.GetFiles(directoryPath);

            for (Int32 index = 0; index < files.Length; index++)
            {
                String file = files[index];

                Console.WriteLine("Deleting file " + file);

                try
                {
                    FileInfo fileInfo = new FileInfo(file);

                    if (fileInfo.IsReadOnly)
                    {
                        fileInfo.IsReadOnly = false;
                    }

                    fileInfo.Delete();
                }
                catch (IOException ex)
                {
                    Console.WriteLine(ex);
                }
                catch (UnauthorizedAccessException ex)
                {
                    Console.WriteLine(ex);
                }
            }

            Console.WriteLine("Deleting directory " + directoryPath);

            try
            {
                Directory.Delete(directoryPath, true);
            }
            catch (IOException ex)
            {
                Console.WriteLine(ex);
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine(ex);
            }
        }

        private static void Main(String[] args)
        {
            foreach (String argument in args)
            {
                if (Directory.Exists(argument))
                {
                    ProcessDirectory(argument);
                }
            }

            Console.WriteLine("Completed");
        }

        private static void ProcessChildDirectory(String directoryPath, String childDirectory)
        {
            String[] directories = Directory.GetDirectories(directoryPath, childDirectory, SearchOption.AllDirectories);

            foreach (String directory in directories)
            {
                DeleteDirectory(directory);
            }
        }

        private static void ProcessDirectory(String directoryPath)
        {
            ProcessChildDirectory(directoryPath, "bin");
            ProcessChildDirectory(directoryPath, "obj");
            ProcessChildDirectory(directoryPath, "TestResults");
        }
    }
}

The best way to hook this up is via the Visual Studio External Tools dialog by passing in the current solutions directory path.

image

This method also allows you to output the console results to the Output window for a more integrated experience.

NOTE: It’s a good idea to run a Visual Studio clean from the build menu before running this tool to get Visual Studio to release any locks it has on files in these directories. We haven’t had any assembly version conflicts since running this tool.

You can either compile the above yourself or grab the executable below.

Neovolve.SolutionCleaner.zip (2.61 kb)

Tags: