View mode: basic / threaded / horizontal-split · Log in · Help
February 12, 2003
Recursive exceptions
Someone mentioned that exceptions should be allowed to be thrown also
when an exception is active, that is, during the unwinding of stack.

As we know, in C++ and probably in D, too, throwing exceptions during
stack unwinding results in a catastrophe of some kind, probably
abort().

So, what happens when an exception escapes a member function of a
class, and leaves that object's invariant in a bad state?

class BadlyBehaving
{
       foo()
       {
               locked = true;
               // ...
               throw new Whatever();
               // ...
               locked = false;
       }

       bit locked = false;

       invariant()
       {
               assert(!locked);
       }
}

f()
{
       BadlyBehaving b;

       try {
               b.foo();
       } catch (Whatever x) {
               // handle whatever
       }

       // but now b is in a bad state
}

now f() calls b.foo(), which causes an exception to be thrown, and
when it exits the scope of BadlyBehaving.foo(), the class invariant is
violated.  Hence, InvariantException or similar should be thrown,
but an exception is already being thrown so we have a problem.

If two (or more) exceptions could be active at the same time, this
would allow us to catch the invariant violation.  Which is nice if
you want to have some kind of exception safety guarantees.

-Antti
February 12, 2003
Re: Recursive exceptions
"Antti Sykari" <jsykari@gamma.hut.fi> wrote in message
news:86y94m0y4f.fsf@hoastest1-8c.hoasnet.inet.fi...
>
> So, what happens when an exception escapes a member function of a
> class, and leaves that object's invariant in a bad state?
>
> class BadlyBehaving
> {
>         foo()
>         {
>                 locked = true;
>                 // ...
>                 throw new Whatever();
>                 // ...
>                 locked = false;
>         }

In this case, you have presumably detected a condition that requires you to
throw a Whatever exception.  It is, therefore, your responsibility to
reestablish the invariant prior to throwing such an exception.

        foo()
        {
                locked = true;
                // ...
                if (condition == true)
               {
                  locked = false;
                  throw new Whatever();
                }
                // ...
                locked = false;
        }


If, on the other hand, you are simply calling another method which itself
may throw some exception, then it would seem to be your responsibility to
provide an appropriate catch handler in foo() to reestablish the invariant
in case that exception is thrown.  Alternatively, you might get away with
reestablishing the invariant in the finally clause.

        foo()
        {
                locked = true;
                // ...
                try
               {
                  bar();  // May raise an exception
                }
                catch (Whatever ex)
               {
                  locked = false;
                }
                // ...
                locked = false;
        }

Eiffel has the notion of "exception correctness" (Eiffel: The Language,
section 15.10, page 258), which is similar to what I described above.


Ken Carpenter
February 12, 2003
Re: Recursive exceptions
Antti Sykari <jsykari@gamma.hut.fi> writes:

> Someone mentioned that exceptions should be allowed to be thrown also
> when an exception is active, that is, during the unwinding of stack.

A more precise formulation of the problem, or an abstract, if you
will:

Exception handling allows a programming language to concentrate its
error-handling code in one place, instead of sprinkling it around in a
modularity-violating and bug-prone manner.  It works well when the
exception patterns are simple and well-behaved.  But what if another
exception occurs when we are recovering from an exception?  One
solution is to abort the execution, such as C++ does.  Indeed, one
basic rule in C++ is: "Don't throw exceptions in destructors."
However, this seems like an arbitrary and unnecessarily restrictive
rule.  There are plenty of reasons why one might want to throw in
destructors; for example, class invariants can be modeled as auto
classes which throw an InvariantException in their destructor.

We can contrast this with interrupt handling in operating systems.
When an interrupt occurs, we store the current execution context and
proceed to handle the interrupt.  After that, we return from the
interrupt handler and resumt the execution.  But another interrupt can
occur whenever we are handling an interrupt.  What to do then?  One
solution is to disable the interrupts while handling the interrupt.
Another one is to put the interrupts in queue (usually this is done
when the actions required by them are of a lower priority then the
current one), and yet another -- if the new interrupt is of higher
priority -- is to store the current context and start to handle the
new one, and process the current interrupt after the more urgent one.

Exceptions are a bit different from interrupts, but my claim here is
that one should prepare for the situation where we must process
exceptions upon exceptions upon exceptions -- an exceptionally
exceptional situation, but possible anyhow.

The obvious question I didn't have time to address in the previous
post was:

* What is the behavior of multiple simultaneous exceptions? *

or "How on Earth could it work?"

For this I have the following rather straightforward proposal,
probably already invented by someone else in some other language:

At all times of stack unwinding, we have a list of active exceptions.
We proceed to unwind the stack and call the destructors of auto
classes. (* -- see comment below)

If a new exception is thrown during the process, it is added
to the list of active exceptions.

Whenever a handler is encountered, it is matched against the active
exceptions and either the most specialized exception or the first
matching exception in the list is given to the handler.  (I'm can't
decide my side on this -- opinions?)  Handler code is executed and the
handled exception is removed from the active exceptions.  The
unwinding process is continued until there are no active exceptions
left, after which the execution is continued after the last matching
handler.

I suppose it can be proved that with the unwinding method described
above, every thrown exception propagates to the nearest exception
handler that can handle it, assuming that each exception handler can
handle precisely one exception.

If multiple active exceptions are allowed, there would be a need for
catch-all exception handlers, which could be used effectively to
insulate the damage to just one subsystem.  Obviously, the top-level
catch-all handler should be like this.

An example top-level handler:

try {
 // do things
} catch_all (Exception e) {
 print("Caught exception ", e, ":\n");
 print(e.stackTrace());
}

(*) Checking the invariant of the class can be effectively modeled as
an "auto" object which is implicitly placed in each member function;
in constructor it acquires a recursive mutex, and if it was the first
one to grab it, it checks the invariant.  (This would not be needed in
a completely type-safe world - but you can never know if the object is
modified by a pointer gone haywire or something) In the destructor it
releases the mutex, and if it's the last one, it checks the invariant.
If the invariant is false, it throws an InvariantException.

Suppose we have the previous example, quoted below with line numbers.
Also suppose we have nice stack traces as in Java :)
The program

int main() { f(); }

would, then, result in:

Got Exception InvariantException()
 thrown in line 16 (BadlyBehaving::invariant())
 at line xxx (stack unwind from exception Whatever, thrown at line 7
    (BadlyBehaving::foo))
 at line yyy
 at ... hmm, not quite sure what would come here actually :)
    but the idea is like this.

I can only assume that stack traces come with all kinds of
complications which I cannot think of, but the general idea sounds
implementable to me.  If it is actually needed is a completely
different question and probably more of a question of opinion than
anything else.  And as a disclaimer I can say that I've never
implemented an exception handling mechanism so I might be wrong.

-Antti

The code:

1 > class BadlyBehaving
  > {
  >         foo()
  >         {
5 >                 locked = true;
  >                 // ...
  >                 throw new Whatever();
  >                 // ...
  >                 locked = false;
10 >         }
  >
  >         bit locked = false;
  >
  >         invariant()
15 >         {
  >                 assert(!locked);
  >         }
  > }
  >
20 > f()
  > {
  >         BadlyBehaving b;
  >
  >         try {
25 >                 b.foo();
  >         } catch (Whatever x) {
  >                 // handle whatever
  >         }
  >
30 >         // but now b is in a bad state
  > }
>
> now f() calls b.foo(), which causes an exception to be thrown, and
> when it exits the scope of BadlyBehaving.foo(), the class invariant is
> violated.  Hence, InvariantException or similar should be thrown,
> but an exception is already being thrown so we have a problem.
>
> If two (or more) exceptions could be active at the same time, this
> would allow us to catch the invariant violation.  Which is nice if
> you want to have some kind of exception safety guarantees.
>
> -Antti
February 12, 2003
Re: Recursive exceptions
"Ken Carpenter" <kencr@shaw.ca> writes:

>>         foo()
>>         {
>>                 locked = true;
>>                 // ...
>>                 throw new Whatever();
>>                 // ...
>>                 locked = false;
>>         }
>
> In this case, you have presumably detected a condition that requires you to
> throw a Whatever exception.  It is, therefore, your responsibility to
> reestablish the invariant prior to throwing such an exception.
> If, on the other hand, you are simply calling another method which itself
> may throw some exception, then it would seem to be your responsibility to
> provide an appropriate catch handler in foo() to reestablish the invariant
> in case that exception is thrown.  Alternatively, you might get away with
> reestablishing the invariant in the finally clause.

Yeah, you can never know what happens - and that's more or less the
point of exceptions that you don't really have to worry about them.

Of course, you *should* prepare for exceptions to preserve the
invariant.  The point is, instead of a crash and a non-informative
message along the lines of "Invariant of BadlyBehaving violated" or
"Exception thrown during unwind" or even "(aborted)", I'd like to get
some knowledge about where and when it happened, and in context of
what exceptions.

> Eiffel has the notion of "exception correctness" (Eiffel: The Language,
> section 15.10, page 258), which is similar to what I described above.

I suppose OOSC has also something about the subject. (I'm currently in
my way through that book)

-Antti
February 12, 2003
Re: Recursive exceptions
In article <86y94m0y4f.fsf@hoastest1-8c.hoasnet.inet.fi>, Antti Sykari says...
>
>
>Someone mentioned that exceptions should be allowed to be thrown also
>when an exception is active, that is, during the unwinding of stack.
>
>As we know, in C++ and probably in D, too, throwing exceptions during
>stack unwinding results in a catastrophe of some kind, probably
>abort().
>
>So, what happens when an exception escapes a member function of a
>class, and leaves that object's invariant in a bad state?
>
>class BadlyBehaving
>{
>        foo()
>        {
>                locked = true;
>                // ...
>                throw new Whatever();
>                // ...
>                locked = false;
>        }
>
>        bit locked = false;
>
>        invariant()
>        {
>                assert(!locked);
>        }
>}
>
>f()
>{
>        BadlyBehaving b;
>
>        try {
>                b.foo();
>        } catch (Whatever x) {
>                // handle whatever
>        }
>
>        // but now b is in a bad state
>}
>
>now f() calls b.foo(), which causes an exception to be thrown, and
>when it exits the scope of BadlyBehaving.foo(), the class invariant is
>violated.  Hence, InvariantException or similar should be thrown,
>but an exception is already being thrown so we have a problem.
>
>If two (or more) exceptions could be active at the same time, this
>would allow us to catch the invariant violation.  Which is nice if
>you want to have some kind of exception safety guarantees.
>
>-Antti
>

Hi,

In Java (since 1.4) exceptions can have a cause (it's just a field in the
Exception class), so you can chain exceptions. This mechanism is used to keep
exception information in a wrapper. But something similar could be used in the
base exception class:

class Exception {
Exception cause();
Exception shadowed();
}

The compiler can do some "magic" to preserve shadowed exceptions:


void doStuff(Thing any) {
breakInvariant();
auto Something some = new Something();
// destructor throws DestructorException

try {
any.doOtherStuff(); // throw AnyException
restoreInvariant();
} finally {
some.doSomeStuff(); // throws OtherException
}
}


The final exception would be InvariantException, shadowing DestructorException,
shadowing OtherException, shadowing AnyException (I think I've got the order
right). Note that any of these can also have the cause field with some other
exception (e.g. AnyException caused by SQLError and OtherException caused by
FileNotFound). IMO the cause field should be filled manually by the programmer
(in the constructor call or maybe a setter), but the shadowed field should be
automatically filled by the compiler (because keeping track of exceptions
shadowing exceptions shadowing... ad infinitum is pretty much boring).

Best regards,
Daniel Yokomiso.

"Philosophy is a battle against the bewitchment of our intelligence by means of 
language."
- Ludwig Wittgenstein
Top | Discussion index | About this forum | D home