Tuesday 30 October 2012

Exception and Stack Trace

I'm using boost::exception. I like the usage pattern they suggest in their docs, I like the concept of having exception types as semantic tags to which I "append" arbitrary error info. In short, I find it brilliant.

However, I've hit a little problem - stack trace. I'd like something similar to Java's and C#'s stack trace, but I'm not ready to dive into backtrace or RtlCaptureStackBackTrace. So, I've decided to create a poor man's stack trace. On the downside, it'll trace only my own code; on the upside, I hope it will be less problematic than a real stack trace (this opinion is not based on actual experience, but rather from what I've found online about it - e.g., this).

I didn't actually consider many alternatives for this one - I needed a structure of an abritrary size that could hold BOOST_CURRENT_FUNCTION, __FILE__ and __LINE__. I've toyed with the idea of placing an std::vector in my exceptions. But I quickly realized that wasn't the way to go.

Why? Suppose we've got a function, LowLevelFunction() that throws a low_level_exception, which has a "stack trace" vector (STV) where we place the error location. This is caught further up the chain by HighLevelFunction(), which adds some more error info, adds its own location to the STV, and rethrows to its caller, HigherLevelFunction(). Now, HigherLevelFunction() was called by HighestLevelFunction(), which doesn't really care about low_level_exceptions; it won't touch anything other than a high_level_exception, except to send it directly to a logger (however, it does want the information contained in low_level_exception). So, HigherLevelFunction() throws a high_level_exception, which has its own STV. Now what do we do? Copy low_level_exception STV entries to high_level_exception? Add HigherLevelFunction()'s location to low_level_exception's STV? Use both STVs? There's no good solution, because the idea isn't sound.

However, as we said, HighestLevelFunction() wants all the relevant info contained in low_level_exception. And all this info must end up on a high_level_exception, since that's what it expects to catch. And the best solution I've found for this is nested exceptions, which is business-as-usual in Java and C#. And, thankfully, also in boost::exception.

We start with this typedef (from here):
typedef boost::error_info<struct tag_nested_exception, boost::exception_ptr>
    nested_exception.
And that means we can now do something like this:
void LowLevelFunction()
{
...
    BOOST_THROW_EXCEPTION(low_level_exception() << error_id(id) << 
        error_message(msg));
}

void HighLevelFunction()
{
...
    catch (low_level_exception& lle)
    {
        BOOST_THROW_EXCEPTION(low_level_exception() << 
            resource_name(res_name) << 
            nested_exception(current_exception()));
    }
}

void HigherLevelFunction()
{
...
    catch (low_level_exception& lle)
    {
        BOOST_THROW_EXCEPTION(high_level_exception() << 
            operation_value(oper_val) << 
            nested_exception(current_exception()));
    }
}

void HighestLevelFunction()
{
...
    catch (high_level_exception& lle)
    {
        // Finally, do something useful with all this info.
        // Hope the logger's still working...
    }
}
So, on the downside, we create two instances of low_level_exception, where we could've used just one. I can live with that, until evidence shows me it's a bad idea.

Yes, I know, there will be times when this may only make matters worse - e.g., if we catch an std::bad_alloc because we ran out of memory, creating exceptions is not the way to go. However, in a situation like this, I'd say our system is most likely headed for a fresh new (re)start, and there's little we can do to change its course.

Next on the list: Take a look at the implementation of diagnostic_information(). I like the completeness of its output, but not so much its formatting, especially when dealing with nested exceptions.

On a side note, I've loved finding out about current_exception(). Finally, I can do something useful here:
SomeImportantClass::~SomeImportantClass()
{
    try
    {
        // Cleanup. May throw
    }
    catch (...)
    {
        // I'd like to log this, but what exactly did we catch?
        // current_exception() to the rescue
    }
}
Oh, and I've been having some fun with perl, regex, and log files. I'll be diving into Boost.Regex soon, so I'm getting used to it in a more "dynamic" environment.

No comments:

Post a Comment