Posts
69
Comments
233
Trackbacks
162
March 2010 Entries
Why Is Faulty Behaviour In The .NET Framework Not Fixed?

Here is the scenario: You have a Windows Form Application that calls a method via Invoke or BeginInvoke which throws exceptions. Now you want to find out where the error did occur and how the method has been called. Here is the output we do get when we call Begin/EndInvoke or simply Invoke

image

The actual code that was executed was like this:

        private void cInvoke_Click(object sender, EventArgs e)
        {
            InvokingFunction(CallMode.Invoke);
        }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
        void InvokingFunction(CallMode mode)
        {
            switch (mode)
            {
                case CallMode.Invoke:
                    this.Invoke(new MethodInvoker(GenerateError));
 

The faulting method is called GenerateError which does throw a NotImplementedException exception and wraps it in a NotSupportedException.

 
        [MethodImpl(MethodImplOptions.NoInlining)]
        void GenerateError()
        {
            F1();
        }
 
        private void F1()
        {
            try
            {
                F2();
            }
            catch (Exception ex)
            {
                throw new NotSupportedException("Outer Exception", ex);
            }
        }
 
        private void F2()
        {
           throw new NotImplementedException("Inner Exception");
        }

It is clear that the method F2 and F1 did actually throw these exceptions but we do not see them in the call stack. If we directly call the InvokingFunction and catch and print the exception we can find out very easily how we did get into this situation. We see methods F1,F2,GenerateError and InvokingFunction directly in the stack trace and we see that actually two exceptions did occur.

image

Here is for comparison what we get from Invoke/EndInvoke

System.NotImplementedException: Inner Exception
    StackTrace:    at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
    at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
    at WindowsFormsApplication1.AppForm.InvokingFunction(CallMode mode)
    at WindowsFormsApplication1.AppForm.cInvoke_Click(Object sender, EventArgs e)
    at System.Windows.Forms.Control.OnClick(EventArgs e)
    at System.Windows.Forms.Button.OnClick(EventArgs e)

The exception message is kept but the stack starts running from our Invoke call and not from the faulting method F2. We have therefore no clue where this exception did occur! The stack starts running at the method MarshaledInvoke because the exception is rethrown with the throw catchedException which resets the stack trace.

That is bad but things are even worse because if previously lets say 5 exceptions did occur .NET will return only the first (innermost) exception. That does mean that we do not only loose the original call stack but all other exceptions and all data contained therein as well.

It is a pity that MS does know about this and simply closes this issue as not important. Programmers will play a lot more around with threads than before thanks to TPL, PLINQ that do come with .NET 4. Multithreading is hyped quit a lot in the press and everybody wants to use threads. But if the .NET Framework makes it nearly impossible to track down the easiest UI multithreading issue I have a problem with that. The problem has been reported but obviously not been solved. .NET 4 Beta 2 did not have changed that dreaded GetBaseException call in MarshaledInvoke to return only the innermost exception of the complete exception stack. It is really time to fix this.

WPF on the other hand does the right thing and wraps the exceptions inside a TargetInvocationException which makes much more sense. But Not everybody uses WPF for its daily work and Windows forms applications will still be used for a long time.

Below is the code to repro the issues shown and how the exceptions can be rendered in a meaningful way. The default Exception.ToString implementation generates a hard to interpret stack if several nested exceptions did occur.

using System;

using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Globalization;
using System.Runtime.CompilerServices;
 
namespace WindowsFormsApplication1
{
    public partial class AppForm : Form
    {
        enum CallMode
        {
            Direct = 0,
            BeginInvoke = 1,
            Invoke = 2
        };
 
        public AppForm()
        {
            InitializeComponent();
            Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
            Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
        }
 
        void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
        {
            cOutput.Text = PrintException(e.Exception, 0, null).ToString();
        }
 
        private void cDirectUnhandled_Click(object sender, EventArgs e)
        {
            InvokingFunction(CallMode.Direct);
        }
 
        private void cDirectCall_Click(object sender, EventArgs e)
        {
            try
            {
                InvokingFunction(CallMode.Direct);
            }
            catch (Exception ex)
            {
                cOutput.Text = PrintException(ex, 0, null).ToString();
            }
        }
 
        private void cInvoke_Click(object sender, EventArgs e)
        {
            InvokingFunction(CallMode.Invoke);
        }
 
        private void cBeginInvokeCall_Click(object sender, EventArgs e)
        {
            InvokingFunction(CallMode.BeginInvoke);
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        void InvokingFunction(CallMode mode)
        {
            switch (mode)
            {
                case CallMode.Direct:
                    GenerateError();
                    break;
                case CallMode.Invoke:
                    this.Invoke(new MethodInvoker(GenerateError));
                    break;
                case CallMode.BeginInvoke:
                    IAsyncResult res = this.BeginInvoke(new MethodInvoker(GenerateError));
                    this.EndInvoke(res);
                    break;
            }
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        void GenerateError()
        {
            F1();
        }
 
        private void F1()
        {
            try
            {
                F2();
            }
            catch (Exception ex)
            {
                throw new NotSupportedException("Outer Exception", ex);
            }
        }
 
        private void F2()
        {
           throw new NotImplementedException("Inner Exception");
        }
 
        StringBuilder PrintException(Exception ex, int identLevel, StringBuilder sb)
        {
            StringBuilder builtStr = sb;
            if( builtStr == null )
                builtStr = new StringBuilder();
 
            if( ex == null )
                return builtStr;
 
 
            WriteLine(builtStr, String.Format("{0}: {1}", ex.GetType().FullName, ex.Message), identLevel);
            WriteLine(builtStr, String.Format("StackTrace: {0}", ShortenStack(ex.StackTrace)), identLevel + 1);
            builtStr.AppendLine();
 
            return PrintException(ex.InnerException, ++identLevel, builtStr);
        }
 
 
 
        void WriteLine(StringBuilder sb, string msg, int identLevel)
        {
            foreach (string trimmedLine in SplitToLines(msg)
                                           .Select( (line) => line.Trim()) )
            {
                for (int i = 0; i < identLevel; i++)
                    sb.Append('\t');
                sb.Append(trimmedLine);
                sb.AppendLine();
            }
        }
 
        string ShortenStack(string stack)
        {
            int nonAppFrames = 0;
            // Skip stack frames not part of our app but include two foreign frames and skip the rest
            // If our stack frame is encountered reset counter to 0
            return SplitToLines(stack)
                              .Where((line) =>
                              {
                                  nonAppFrames = line.Contains("WindowsFormsApplication1") ? 0 : nonAppFrames + 1;
                                  return nonAppFrames < 3;
                              })
                             .Select((line) => line)
                             .Aggregate("", (current, line) => current + line + Environment.NewLine);
        }
 
        static char[] NewLines = Environment.NewLine.ToCharArray();
        string[] SplitToLines(string str)
        {
            return str.Split(NewLines, StringSplitOptions.RemoveEmptyEntries);
        }
    }
}
  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati
Posted On Wednesday, March 17, 2010 10:55 AM | Feedback (0)
Run Your Tests With Any NUnit Version

I always thought that the NUnit test runners and the test assemblies need to reference the same NUnit.Framework version. I wanted to be able to run my test assemblies with the newest GUI runner (currently 2.5.3). Ok so all I need to do is to reference both NUnit versions the newest one and the official for the current project. There is a nice article form Kent Bogart online how to reference the same assembly multiple times with different versions. The magic works by referencing one NUnit assembly with an alias which does prefix all types inside it. Then I could decorate my tests with the TestFixture and Test attribute from both NUnit versions and everything worked fine except that this was ugly. After playing a little bit around to make it simpler I found that I did not need to reference both NUnit.Framework assemblies. The test runners do not require the TestFixture and Test attribute in their specific version. That is really neat since the test runners are instructed by attributes what to do in a declarative way there is really no need to tie the runners to a specific version. At its core NUnit has this little method hidden to find matching TestFixtures and Tests

 

public bool CanBuildFrom(Type type)
{
    if (!(!type.IsAbstract || type.IsSealed))
    {
        return false;
    }


    return (((Reflect.HasAttribute(type,           "NUnit.Framework.TestFixtureAttribute", true) ||

              Reflect.HasMethodWithAttribute(type, "NUnit.Framework.TestAttribute"       , true)) ||

              Reflect.HasMethodWithAttribute(type, "NUnit.Framework.TestCaseAttribute"   , true)) ||

              Reflect.HasMethodWithAttribute(type, "NUnit.Framework.TheoryAttribute"     , true));
}

That is versioning and backwards compatibility at its best. I tell NUnit what to do by decorating my tests classes with NUnit Attributes and the runner executes my intent without the need to bind me to a specific version. The contract between NUnit versions is actually a bit more complex (think of AssertExceptions) but this is also handled nicely by using not the concrete type but simply to check for the catched exception type by string.

What can we learn from this? Versioning can be easy if the contract is small and the users of your library use it in a declarative way (Attributes). Everything beyond it will force you to reference several versions of the same assembly with all its consequences. Type equality is lost between versions so none of your casts will work. That means that you cannot simply use IBigInterface in two versions. You will need a wrapper to call the correct versioned one. To get out of this mess you can use one (and only one) version agnostic driver to encapsulate your business logic from the concrete versions. This is of course more work but as NUnit shows it can be easy. Simplicity is therefore not a nice thing to have but also requirement number one if you intend to make things more complex in version two and want to support any version (older and newer). Any interaction model above easy will not be maintainable. There are different approached to versioning. Below are my own personal observations how versioning works within the  .NET Framwork and NUnit.

 

Versioning Models

1. Bug Fixing and New Isolated Features

When you only need to fix bugs there is no need to break anything. This is especially true when you have a big API surface. Microsoft did this with the .NET Framework 3.0 which did leave the CLR as is but delivered new assemblies for the features WPF, WCF and Windows Workflow Foundations. Their basic model was that the .NET 2.0 assemblies were declared as red assemblies which must not change (well mostly but each change was carefully reviewed to minimize the risk of breaking changes as much as possible) whereas the new green assemblies of .NET 3,3.5 did not have such obligations since they did implement new unrelated features which did not have any impact on the red assemblies.

This is versioning strategy aimed at maximum compatibility and the delivery of new unrelated features. If you have a big API surface you should strive hard to do the same or you will break your customers code with every release.

2. New Breaking Features

There are times when really new things need to be added to an existing product. The .NET Framework 4.0 did change the CLR in many ways which caused subtle different behavior although the API´s remained largely unchanged. Sometimes it is possible to simply recompile an application to make it work (e.g. changed method signature void Func() –> bool Func()) but behavioral changes need much more thought and cannot be automated. To minimize the impact .NET 2.0,3.0,3.5 applications will not automatically use the .NET 4.0 runtime when installed but they will keep using the “old” one.

What is interesting is that a side by side execution model of both CLR versions (2 and 4) within one process is possible. Key to success was total isolation. You will have 2 GCs, 2 JIT compilers, 2 finalizer threads within one process. The two .NET runtimes cannot talk  (except via the usual IPC mechanisms) to each other. Both runtimes share nothing and run independently within the same process. This enables Explorer plugins written for the CLR 2.0 to work even when a CLR 4 plugin is already running inside the Explorer process. The price for isolation is an increased memory footprint because everything is loaded and running two times.

 

3. New Non Breaking Features

It really depends where you break things. NUnit has evolved and many different Assert, Expect… methods have been added. These changes are all localized in the NUnit.Framework assembly which can be easily extended. As long as the test execution contract (TestFixture, Test, AssertException) remains stable it is possible to write test executors which can run tests written for NUnit 10 because the execution contract has not changed.

It is possible to write software which executes other components in a version independent way but this is only feasible if the interaction model is relatively simple.

 

Versioning software is hard and it looks like it will remain hard since you suddenly work in a severely constrained environment when you try to innovate and to keep everything backwards compatible at the same time. These are contradicting goals and do not play well together. The easiest way out of this is to carefully watch what your customers are doing with your software. Minimizing the impact is much easier when you do not need to guess how many people will be broken when this or that is removed.

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati
Posted On Sunday, March 07, 2010 10:33 AM | Feedback (2)