clr20r3: The World’s least helpful runtime error

You’ve got to wonder what people were thinking when they designed .NET’s behaviour when encountering an unhandled exception.  Sensibly, it writes the event log.  Stupidly, it writes garbage you can’t read.  Let’s just appreciate it in all its glory.

clr20r3

Good luck figuring out what the problem was.  (Actually, the server was down, but that’s hardly the point…)  It’s pretty easy to say “Well, you should just catch all exceptions in Main.” but it misses the point.  Any thread failing can cause this to happen, and if you’re using third party libraries they’re not necessarily under your control.  So disciplined coding isn’t going to help you on this one.  Trawling the internet, on the other hand, will.  At least, if you look for long enough.  What you need to do is to put a handler on the domain’s unhandled exception event.  Here’s the code:

private static void LogUnhandledExceptions(string source, int fatalEventId)
{
    AppDomain.CurrentDomain.UnhandledException += delegate(object sender, UnhandledExceptionEventArgs e)
    {
        if (!(e.ExceptionObject is System.Threading.ThreadAbortException))
        {
            Exception exception = e.ExceptionObject as Exception;
string message = exception == null ? e.ExceptionObject == null ? "Missing exception" : e.ToString() : string.Format("Fatal Error {0}: {1}rn{2}", exception.GetType().FullName, exception.Message, exception.StackTrace); try { System.Diagnostics.EventLog.WriteEntry(source, message, System.Diagnostics.EventLogEntryType.Error, fatalEventId); } catch { try { System.Windows.Forms.MessageBox.Show(message, "Fatal Error",
MessageBoxButtons.OK, MessageBoxIcon.Stop); } catch { if (Console.Error != null) { Console.Error.WriteLine(message); } Console.WriteLine(message); } } } }; }

I kid you not, cut and paste this routine into every system you ever write, and make it the absolute first thing that gets called.  Some of this code is probably redundant (is Console.Error ever null, for instance?).  It won’t solve your problem, but at least you’ll be able to see the error message.  Note that this code is not configurable in any way, since it needs to run before configuration.  So log4net et al can’t be used.  Incidentally, the clr20r3 error will still appear, so code discipline is still worth practicing.

Now, I can’t count the number of times I’ve thought that Microsoft had made a bone-headed design decision which later turned out to be quite smart but really, why doesn’t the .NET runtime log a readable error by default?

Always use an absolute path when invoking Binsor

If, like me, you’re using Binsor for configuration, and using the include file mechanism to implement environmental deltas, you’re going to need to refer to the initial file using an absolute path.  The reason for this is, if you don’t, a relative path name within the Binsor script uses the current working directory.  Just to be annoying, this means that all of your code works until the moment you deploy it, and then your service fails to start (this bears no resemblance to a real issue, I can assure you…)

Anyway, here’s the code you need:

private static void ReadRelativePath(IWindsorContainer container, string relativePath)
{
    string location = System.Reflection.Assembly.GetEntryAssembly().Location;
    string directory = Path.GetDirectoryName(location);

    Rhino.Commons.Binsor.BooReader.Read(container, Path.Combine(directory, relativePath));
}

You could potentially fix this by changing the current directory in the program, but that’s the kind of externality I really don’t want to deal with.

Technorati Tags: ,