Topic: Multiple in-flight exceptions
Author: dave@boost-consulting.com (David Abrahams)
Date: Sat, 11 Sep 2004 16:59:36 GMT Raw View
usenet-nospam@nmhq.net (Niklas Matthies) writes:
> Anyway, I'd be glad to hear any opinions.
First, I think if we're going to go down this path it would be better
to have just one "primary" exception, with an additional way to
*explicitly* throw other exceptions in the in-flight set. Trying to
serialize the handling of multiple in-flight exceptions, or prevent
multiple error reports sounds like a nightmare in your model. Second,
I have some concern about continuing after an exception that occurs
during a destructor. That's almost certainly not a situation anyone
is going to code for explicitly. Last, it's not clear whether catch
blocks and destructors ought to be treated differently. If the
cleanup code in a catch(...) block throws should that be the new
in-flight exception, or should it just be added to the set? I
presume the latter is how cleanup code in a destructor works.
--
Dave Abrahams
Boost Consulting
http://www.boost-consulting.com
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: usenet-nospam@nmhq.net (Niklas Matthies)
Date: Sun, 12 Sep 2004 16:50:58 GMT Raw View
On 2004-09-11 16:59, David Abrahams wrote:
> usenet-nospam@nmhq.net (Niklas Matthies) writes:
>
>> Anyway, I'd be glad to hear any opinions.
>
> First, I think if we're going to go down this path it would be better
> to have just one "primary" exception, with an additional way to
> *explicitly* throw other exceptions in the in-flight set.
I suppose this is just a terminology issue, but right before you throw
an exception, there are no exceptions in-flight. At least that's what
I intended "in-flight" to mean, to distinguish from exceptions whose
"flight" is currently suspended due to a destructor or catch clause.
Is there a better terminology to make this distinction?
To repeat the model: A throw statement creates a 1-element in-flight
set. It's only when the stack walkback that ensues exits a scope where
another set of thrown exceptions is waiting (i.e. the scope of a
destructor or of a catch clause), it is merged with that set, and the
result becomes the new in-flight set.
So what you mean is to have two variants of the throw statement, one
which behaves as described in the previous paragraph, and one which
tags the newly thrown exception as "don't merge with any waiting set
that is encountered, but instead call std::teminate()"?
My feeling is that this is wrong, because it means that the function
that throws makes assumptions about the context in which it is called,
and about how the error that the exception signals ought to be handled.
If you don't want exceptions to escape catch blocks or destructors,
you can still catch them there.
> Trying to serialize the handling of multiple in-flight exceptions,
> or prevent multiple error reports sounds like a nightmare in your
> model.
These multiple exceptions _are_ multiple errors, in my opinion.
The fact that some code is executed due to stack walkback does not
imply that any failure within that code has any relation to the
failure that caused the exception that initiated the stack walkback.
Sometimes the errors are indeed related, but by no means necessarily,
and even when they are related, there is no general way to find out if
and how.
Given this, and unless you want to silently discard or prematurely
"handle" errors (as is currently necessitated by the de-facto
requirement that destructors do not throw), there is a genuine need to
deal with the situation of multiple errors anyway. It's the simple
consequence of the fact that after one error occurs, we decide to
continue with executing some operations that may also fail.
> Second, I have some concern about continuing after an exception that
> occurs during a destructor. That's almost certainly not a situation
> anyone is going to code for explicitly.
One shouldn't need to code for that situation explicitly, I believe.
Whether a failure occurs inside or outside of a destructor is not
that fundamentally different. To the outer code, both mean that the
operation failed.
To give one example: Let's say that we have an object that represents
a connection (network, database, whatever) over which we send data.
The object's destructor closes the connection, but this operation
fails, say because sending a final protocol-internal "we're done"
message failed, or because releasing a lock associated with the
connection failed. What is so dramatically different about these
failures that the program should stop dead in its tracks, compared to
failures in regular member functions?
> Last, it's not clear whether catch blocks and destructors ought to
> be treated differently. If the cleanup code in a catch(...) block
> throws should that be the new in-flight exception, or should it just
> be added to the set?
The latter, as I described in the first posting.
An exception has two aspects: The stack-unwinding aspect, i.e.
aborting the current operation, and the error information aspect, i.e.
data which describes or characterizes the error (this includes the
type of exception objects). With sets of exceptions, we merely enrich
the error information, if you will.
> I presume the latter is how cleanup code in a destructor works.
I'm not quite sure I follow here.
-- Niklas Matthies
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: dave@boost-consulting.com (David Abrahams)
Date: Sun, 12 Sep 2004 20:17:46 GMT Raw View
usenet-nospam@nmhq.net (Niklas Matthies) writes:
> On 2004-09-11 16:59, David Abrahams wrote:
>> usenet-nospam@nmhq.net (Niklas Matthies) writes:
>>
>>> Anyway, I'd be glad to hear any opinions.
>>
>> First, I think if we're going to go down this path it would be better
>> to have just one "primary" exception, with an additional way to
>> *explicitly* throw other exceptions in the in-flight set.
>
> I suppose this is just a terminology issue, but right before you throw
> an exception, there are no exceptions in-flight. At least that's what
> I intended "in-flight" to mean, to distinguish from exceptions whose
> "flight" is currently suspended due to a destructor or catch clause.
> Is there a better terminology to make this distinction?
>
> To repeat the model: A throw statement creates a 1-element in-flight
> set. It's only when the stack walkback that ensues exits a scope where
> another set of thrown exceptions is waiting (i.e. the scope of a
> destructor or of a catch clause), it is merged with that set, and the
> result becomes the new in-flight set.
I *think* I understood you the first time.
> So what you mean is to have two variants of the throw statement, one
> which behaves as described in the previous paragraph, and one which
> tags the newly thrown exception as "don't merge with any waiting set
> that is encountered, but instead call std::teminate()"?
Not at all. What I mean is that at any time, only one "primary"
exception from the "in-flight set" can be caught during exception
unwinding. If you want to establish a new context in which one of the
other exceptions can be caught, you do something like:
throw++;
or perhaps you drop out of a catch block handling the primary
exception and do:
throw;
I'm not entirely sure about how it should play out, but I want the
programmer to get some control over whether and how the "secondary"
exceptions are handled, and especially in what order they are handled.
> My feeling is that this is wrong, because it means that the function
> that throws makes assumptions about the context in which it is
> called, and about how the error that the exception signals ought to
> be handled. If you don't want exceptions to escape catch blocks or
> destructors, you can still catch them there.
I'm going to assume that argument was based on a misunderstanding of
my proposal.
>> Trying to serialize the handling of multiple in-flight exceptions,
>> or prevent multiple error reports sounds like a nightmare in your
>> model.
>
> These multiple exceptions _are_ multiple errors, in my opinion.
Sure.
> The fact that some code is executed due to stack walkback does not
> imply that any failure within that code has any relation to the
> failure that caused the exception that initiated the stack walkback.
Right.
> Sometimes the errors are indeed related, but by no means necessarily,
> and even when they are related, there is no general way to find out if
> and how.
Right.
> Given this, and unless you want to silently discard or prematurely
> "handle" errors (as is currently necessitated by the de-facto
> requirement that destructors do not throw), there is a genuine need
> to deal with the situation of multiple errors anyway. It's the
> simple consequence of the fact that after one error occurs, we
> decide to continue with executing some operations that may also
> fail.
Of course.
>> Second, I have some concern about continuing after an exception that
>> occurs during a destructor. That's almost certainly not a situation
>> anyone is going to code for explicitly.
>
> One shouldn't need to code for that situation explicitly, I believe.
> Whether a failure occurs inside or outside of a destructor is not
> that fundamentally different. To the outer code, both mean that the
> operation failed.
Not really. Destructors have an important role in restoring program
invariants. If a destructor is interrupted and fails to complete,
it's unclear to me what the program can do that's correct. Here are
two examples:
Suppose a derived class throws from its ctor in a new-expression.
Then the fully-constructed base class' dtor throws again as unwinding
is dismantling the object. Is the memory freed? Seems to me you have
to leak it, because the C++ standard says that if you re-use the
memory without destroying all classes with nontrivial dtors the
behavior is undefined.
Suppose an RAII class' dtor throws during stack unwinding. That class
was responsible for maintaining some invariant, but it didn't complete
successfully. Is the program still in a valid state? How do you
recover from this condition? You can't re-try destroying the object.
> To give one example: Let's say that we have an object that
> represents a connection (network, database, whatever) over which we
> send data. The object's destructor closes the connection, but this
> operation fails, say because sending a final protocol-internal
> "we're done" message failed, or because releasing a lock associated
> with the connection failed. What is so dramatically different about
> these failures that the program should stop dead in its tracks,
> compared to failures in regular member functions?
It depends whether the fact that the connection is closed is important
to the program invariants. If it isn't, and the destructor doesn't
do *anything* else of consequence, it's probably fine to throw. But
you don't need multiple exceptions in flight to handle that condition.
>> Last, it's not clear whether catch blocks and destructors ought to
>> be treated differently. If the cleanup code in a catch(...) block
>> throws should that be the new in-flight exception, or should it
>> just be added to the set?
>
> The latter, as I described in the first posting.
That's a significant change in current behavior. How will that be
distinguished inside exception translators?
try {
low_level::foo();
}
catch(low_level::exception& e)
{
throw high_level::exception(e.whatever());
}
This idiom is common right now, and the throw may occur in some
helper function:
try {
low_level::foo();
}
catch(low_level::exception& e)
{
throw_high_level_exception(e.whatever());
}
> An exception has two aspects: The stack-unwinding aspect, i.e.
> aborting the current operation, and the error information aspect,
> i.e. data which describes or characterizes the error (this includes
> the type of exception objects). With sets of exceptions, we merely
> enrich the error information, if you will.
Sometimes people legitimately want to mask some of that information,
as in the translation case.
>> I presume the latter is how cleanup code in a destructor works.
>
> I'm not quite sure I follow here.
I'm saying that if cleanup code in a destructor called during
stack-unwinding throws, you'd like that exception to be added to the
in-flight set. I'm saying that try/catch might be a different case.
--
Dave Abrahams
Boost Consulting
http://www.boost-consulting.com
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]