www.digitalmars.com         C & C++   DMDScript  

D - Exception Handling extension

reply Mac Reiter <Mac_member pathlink.com> writes:
One of the things that I like about Visual Basic's and Eiffel's error handling
mechanism is the ability to resume back to where the error occurred.  This
allows a library to "signal" a problem that it cannot resolve on its own back up
to the application (the part that even C++ exceptions can handle) and allows the
application to attempt to fix the problem and continue at the point where the
problem occurred (the part that C++ exceptions cannot handle).  The application
does not understand the workings of the library well enough to know how to
continue the operation, so without a "resume" capability, about the only thing
the application can really do is log the error and bail out.

This is not to say that I think VB's or Eiffel's error handling system is
complete.  They both have a very limited set of controls for where you "resume"
to, and they both expect the application (error handler) to know where to resume
to, which I've already said is impossible (remember encapsulation and data/algo
hiding?)

I looked at D's "Handling Errors" page, which described how the system worked,
but did not give syntax.  I'm sure the syntax is out there somewhere, but I
kinda wanted to get this out in a hurry, so I'm going to borrow a variation of
C++ try/catch syntax that also supports "finally".

The basic idea I would like to see is a way for "throw" to specify a
"transaction".  Much like in database programming, if a transaction fails,
attempts are made to rectify the problem and the transaction is restarted at the
beginning of the transaction.  I am not sure about the keyword "transaction",
however, because in a DB transaction, you are able to undo the partial results
of the transaction before restarting.  In the general programming case you
cannot do this, because you cannot know the full ramifications of the processing
that may have occurred partially through the transaction.  It would be up to the
"transaction" designer to design it in a "redoable" way (I believe the
mathematical term is "idempotent", but I could easily be mistaken).

This allows error handling to be split sanely between the library and the
application.  The application is responsible for trying to fix the situation (if
the library was able to fix it, it would have done so without throwing an error
up into the application).  The library is responsible for knowing how much work
it has to redo after the problem is addressed (I won't say fixed, since that
isn't guaranteed) (the application cannot know where to resume because of
encapsulation).

I'll take new() as the ultimate library function that needs error handling:

main()
{
// "register" an error handler
try
{
// stuff

// (1)
SomeClass S = new SomeClass;

// (10)

// (11)
try
{
// (12)
SomeClass optional_S = new SomeClass;

// do something with optional_S;
// In our example, the allocation will fail, be ignored,
// and we will never get here.
}
catch (out_of_memory E) // (16)
{
// (17)
if (flush_cached_results(E.memory_requested))
{
resume;
}
else
{
(18)
ignore;
}
}

// (19)
float x = 10.0, y = 0.0;
float z = x/y; // (20)

// (23)
// more stuff
} 
catch (out_of_memory E) // (5)
{
// (6)
if (flush_cached_results(E.memory_requested))
{
// (7)
resume;
}
// (*)
}
catch (div_by_zero DZ) // (21)
{
// (22)
ignore; // or resume, or go_on, or something...  See below
}
finally
{
// (24)
// presumably something to do here...
}
}

// OK, this is cheesy, but I've gotta type something -- you know
// what I mean...
void * new(unsigned long NumBytes)
{
void * chunk;

// (2), (13)
transaction
{
// (3), (8), (14)
chunk = malloc(NumBytes);
} if (chunk == NULL) throw(out_of_memory(NumBytes)); // (4), (9), (15)
}



OK, here's an attempt at explaining what that means and what all the numbers are
for:
(1) Try to allocate some memory.
(2) Register the beginning of a transaction.
(3) Perform the work of the transaction.  For sake of illustration, assume the
GC runs and still can't get enough memory, so chunk receives the value NULL.
(4) End of transaction, we perform a conditional throw.  Here the condition is
true, so we have to throw an out_of_memory exception.  I assume an ability to
pass information along with the exception -- the exact nature of that
information passage is ignored for this illustration -- if I have to encode the
data in a string and decode it later that works...
(5) The nearest error handler catches it.
(6) Attempt to remedy the problem.  For illustration, assume that the
application has some cached results which are a speed convenience but not
strictly necessary.  Also assume that "flush_cached_results" will return "true"
(however you wish to encode it) if it successfully frees the requested amount of
memory, and false otherwise.
(7) In this illustration, the flush worked, so we can resume.  This will take us
back to:
(8) The transaction is restarted.  This time malloc works (at least for this
example -- in a multithreaded/SMP system someone could have come in and scarfed
up memory, causing it to fail again, in which case we loop around again)
(9) We test the conditional throw and see that no problem exists, so we exit
normally.
(10) When new() returns, we continue processing as if nothing untoward happened.
(11) Let's see how we can "ignore" errors.  We can stack "try/catch", so we can
register a new error handler that is closer to the problem than our earlier
handler.
(12) Here we do some optional work that would be nice if we can do it, but can
be skipped if not possible.  We'll try to allocate some stuff to do this work,
and for illustration we will assume that there isn't enough memory.
(13) Back in new(), we start a transaction.
(14) malloc fails, chunk == NULL.
(15) We throw the exception.
(16) The closer handler catches it.
(17) Tries to flush cache, which doesn't work at this point -- we've already
flushed it...  So we go to the else.
(18) And ignores the exception.  "ignore" means that this try block and
everything that it has spawned are going to exit from here, so the stack can be
unwound, raii references can finalize, etc.  Execution basically continues
straight down from the ignore (skipping over any other "catch" clauses that may
follow it, and making sure to execute any "finally" clause that may be present).
(19) After ignoring the previous exception, we show up here.
(20) OK, this may or may not throw an exception in D, since D can handle NAN,
but roll with me on it and assume that a divide_by_zero exception is thrown.
This is just to show how multiple exception handlers could be registered at
once.  
(21) Catches the divide_by_zero
(22) And ignores it, since we were just being silly anyway.  I have a slight
problem here: "ignore" (as I've described it) will cause the outer try/catch to
exit, which means we won't get to (23), which I really intended to do.  "resume"
could be forced into service as a special case (it was an FPU exception, rather
than the throw exception that we have already described), but I hate special
cases on principal.  VB uses "resume next", and I have to admit I considered
"continue", but it is already taken.  Further reflection, however, is beginning
to convince me that I didn't actually want to get to (23) -- after all, my
variables are in an error condition at that point, so anything I do after that
is going to be incorrect, so maybe I should just bail out of this try/catch
section.  If I *really* wanted to get to (23), I could put the exception-causing
calculations inside their own try/catch like I did with optional_S.  OK, sorry
for the inline babbling.  I'm thinking about this as I type it...
(23) We never get here, because the exception and the ignore caused us to exit
the try/catch block.  But not before we get to:
(24) Executing any relevant "finally" clause before we exit.

(*) We don't actually get to this point in the illustration, but if we did, we
would have to pass the exception up to the next higher handler.  Basically, we
have caught the exception, tried to remedy the situation, and failed.  We cannot
resume (problem not fixed), we don't have authority to ignore, so all we can do
is let it go to a higher authority.  I considered putting a rethrow() here, but
that potentially changes the concept of "who/where" originally threw the
exception (I believe in C++ rethrow is specifically used to rethrow the original
exception without modifying it in any way, but I'm not certain).  If you require
rethrow, however, you have to define what happens if the user forgets to put
anything in this case.  Did they mean to just wander out of the handler (that
would be "ignore")?  You could say that the programmer is required to put either
resume, ignore, or rethrow at every "exit point" of the handler, but I hate to
put that extra load on the compiler (having to be sure it finds every possible
path through the handler).  I would rather just have an implicit rethrow at the
bottom of every handler, because if you couldn't handle it, you end up down
there anyway, so you pass it on to the next bigger handler.  Having said that,
it may be desirable to have a rethrow keyword so that if you detect early on
that you can't handle it, you can just give up there -- that is somewhat like
having a return in the middle of a function, which is generally considered bad
style, but frequently just too bloody useful...

There are some tricks here:
1. If you have RAII references, you can't finalize them until either the
exception system has wandered up the handler stack to the top of the program and
is about to bail out anyway, or you hit an ignore.

2. To avoid infinite loops in the resume system, it is highly likely that you
would want to keep track of which handler did the resume.  If the transaction is
forced to throw again (see point (8) and the discussion of multithreading/SMP
changing the state of the system while you are trying to handle the exception),
it is probably preferable to throw past the handler that has already tried.  If
you just throw to the same handlers, you may end up back in the transaction with
no further improvement in your situation.  A poorly written handler could also
cause you to get stuck forever between the transaction and the handler.  If you
forcibly bypass the handler that resumed and go to the next "higher" handler in
the handler stack, you are guaranteed to eventually get out of this situation
(although it might mean you get out by blowing up the application, but at least
you don't lock up).  This also allows programmers to make staged handlers.
Outer handlers can take drastic measures as a last ditch attempt to continue
running, and inner handlers can try "nicer" procedures.

I seem to remember that D uses a single Exception type that just contains a
string.  If that is correct, then my syntax for stacked catch statements that
differentiate based on exception type is invalid.  You could do the equivalent
with a:

catch(exception E)
{
switch (E.str)
{
case "out of memory" : flush();
resume;
break; // not really necessary.
case "divide by zero": ignore;
break; // not needed either...
}
}

I was just thinking about how I'd like to change C++ exceptions, so I fell back
on C++ syntax/semantics.

Any thoughts?  Since this impacts raii references, I figured I should get it out
there for discussion before raii got locked down.

I have never really found any exception handling system that made my life any
easier than checking return codes and handling it inline.  I think that this
method, or at least something like it, would give me enough control to actually
write code the way you are supposed to with exceptions:

main()
{
try
{
do_something();

more();

even_more();
}
catch(E)
{
// fix any problems that occur, and let work continue
}
}

You can write code like that in C++, except that you can't get to the part about
"and let work continue" unless you are willing to put more code in than you
would have needed to check return codes inline.

I'm not sure what this does to Walter ;)  I *think* that storing an instruction
counter (to the beginning of the transaction) and stack pointer (to wherever the
transaction exists) is enough to get back, and then you just have to remember
not to unroll the stack until you hit an ignore or exit normally out of the
functions.  The exception handler stack was already present, I think.  Of
course, there is the problem of being in the middle of one transaction when you
start another transaction, which means you would need a stack of active
transactions (instruction counter / stack pointer pairs) to really handle this.

Anybody else like this?  Am I out of my mind?  Is this going to double the
complexity of the compiler (I don't think so, but I'm frequently mistaken about
what causes compiler complexity)?

Mac
Sep 17 2002
next sibling parent reply Joe Battelle <Joe_member pathlink.com> writes:
Resumption is one of those ideas that sounds good, but turns out not to buy you
very much.  I have a lot of experience with a couple Smalltalk
exception-handling-implementations that support resumption.  Frankly, there just
aren't that many times when you can resume cleanly without fixing things up.
And the fixup may need to reconstruct a large set of objects, so you end up with
reinit methods--blech.

Look at Erlang (erlang.org) if you are interested in handling resumption well.
They don't do it in the exception handler; they let the thread crash and a
monitor thread (that receives notifications on exceptions in another thread)
recreates it.  I think this is the better approach.
Sep 17 2002
parent reply Mac Reiter <Mac_member pathlink.com> writes:
In article <am7ij4$285a$1 digitaldaemon.com>, Joe Battelle says...
Resumption is one of those ideas that sounds good, but turns out not to buy you
very much.  I have a lot of experience with a couple Smalltalk
exception-handling-implementations that support resumption.  Frankly, there just
aren't that many times when you can resume cleanly without fixing things up.
And the fixup may need to reconstruct a large set of objects, so you end up with
reinit methods--blech.
"without fixing things up"? I fully intended to fix things up. That's what the transaction block was for. It does mean that you end up having to have some (re)init code inside your transaction block, but it doesn't have to be a separate method. It may be slightly tricky to handle "first pass" vs. "resume pass" behavior, but you can always get around that with: bool been_here = false; transaction { if (been_here) { // re-init } else { been_here = true; // do real work } } Dunno how that compares to a reinit method. At least it's nearby, rather than in another chunk of code... I tried to look up SmallTalk exceptions, but the online docs I could find were not especially helpful. What I could find suggested that when you resume after an exception in SmallTalk you restart the entire function where the exception was thrown. This granularity is similar to Eiffel, and I find it too annoying to use. What I was trying to add to the resumption system, and which is new at least as near as I can tell, is the transaction concept, where the lower-level code (the exception causing code) can specify how and where resumption takes place. My example was not very good at illustrating that aspect, since my transaction only had a single line of code. I was trying to keep it brief (can you imagine if my original post had been longer and more complicated?) But you can imagine a transaction that has more than one line (maybe the transaction includes making a GC pass before attempting the alloc, if you are doing this at the very low level allocation routine). You can probably also imagine a function where the entire function does not need to be restarted, but only a relatively small portion does. I would rather not have to make another function (with all of the various declarations that I have to maintain) to provide the restarting scope -- I would rather simply mark it as a transaction and go on. To the best of my knowledge, all other exception mechanisms have either no resumption mechanism or a very limited set of places where resumption can occur (the common locations I have seen are: beginning of function, same line, following line). I think that this restriction is part of why resumption is so difficult to use well.
Look at Erlang (erlang.org) if you are interested in handling resumption well.
They don't do it in the exception handler; they let the thread crash and a
monitor thread (that receives notifications on exceptions in another thread)
recreates it.  I think this is the better approach.
You could create an equivalent system with my approach (or with a minor extension of my approach -- see below). If your entire thread function is a transaction, a simple global handler could take the place of your monitor thread. An exception in a thread would then throw to the global handler, which would resume back at the beginning of the thread, "recreating" it. Technically this would require breaking an implicit rule (which I didn't specify in the original post, so I'm not sure how bad it is to break it...). The system I originally suggested would limit "throw" to the conditional end of a transaction only (only valid as part of "transaction{}if () throw()"). To make the Erlang-emulator from the previous paragraph, you would need the ability to throw without a tied in transaction: void threadfunc(void * context) { transaction { // do something // oops, failure, kick it out and try again if (problem) throw(oops); // do something else } if (overall_problem) throw(biggie); } void main() { try { beginthread(threadfunc, NULL); } catch(oops O) { resume; } } The "oops" exception that was thrown naked would be caught by the "catch(oops O)". The "resume" in that handler would resume to the beginning of the currently active transaction (or to the closest open transaction to the point of the throw -- multithreading makes the simple stack approach infeasible (*1)), which would restart the thread function from the beginning, just like Erlang. I'm sure there are lots of issues I am overlooking, which is why I wanted to post this and see what other people think. I appreciate any feedback, especially from those of you who have tried to use exceptions and realized just how limiting they are in all these other languages. Exceptions and multithreading are funky all the way around, and I need to think about that more. The "naked throw" issue is another thing I overlooked. I had considered it and was originally thinking that it would be equivalent to an empty transaction, or a non-resumable exception. Non-resumable exceptions would cause any handler that tried to resume to rethrow instead. Arguably, this isn't even special handling: logically, you would let the resume go back to the throw (empty transaction), which would throw again (no conditional) which would go to the next higher handler (see original post). This is just what rethrow would do, so it is consistent. Of course, that leaves us with no way to do the Erlang behavior, unless we have a way to break out of a "transaction level", like "break" breaks out of scoping levels. I am definitely still thinking about it, and will probably attempt to roll something like this into my "fake exceptions" system that I developed for CE (Microsoft's C++ compiler for CE does not support exceptions or RTTI, which makes life somewhat entertaining some days...). Having recently re-examined the ANSI specs on setjmp/longjmp, I am once again unimpressed by them. Local automatic (in the C sense, not the newly forming D raii sense) variables can have undefined values after you return to a context through longjmp. This makes the system somewhat less than useful for making reusable code. Their answer is that local variables that you need should be flagged as volatile, but I can't do that to the application's functions from inside my reusable library... I would like to figure out some way to do "finally" from C++ codespace as well, and I've gotten close, but there are some niggly details that make it rather surprising to handle. Anyway, when that gets done and my coworkers and I get a chance to start using it, we will be able to give a better feel for how useful transaction based programming is. (Transaction based database stuff is really useful, so I would expect transaction based programming to have at least moderate usefulness, as long as it is done right) Mac (*1) Multithreading and transaction stacks: Basically this means that each thread needs its own transaction stack (or transactions in the global stack need a thread ID so that you can work with it as an interleaved stack), and that throw will have to extract this information from the thread-local or interleaved stack and include it in the exception object. And you probably want a built in synchronization for your handlers, since they could end up trying to handle exceptions from multiple threads simultaneously. This is all much easier if thread creation and management is part of the language, like in D, instead of sitting in a library like in C/C++. I'm sure there are a ton of other issues that haven't occurred to me yet. I've only been thinking about this kind of solution off and on for around a day...
Sep 17 2002
parent reply Mac Reiter <Mac_member pathlink.com> writes:
As a followup, having thought about the Erlang emulation system some more:

I would first like to state that killing and restarting an entire thread seems
to be an extremely large grain solution (and rather draconian).

But, if you want/need to do that, you can also do the Erlang emulation without
naked throws or adding new transaction-escaping keywords:

void threadfunc(void * context)
{
transaction
{
try // get a local handler to give us the
{   // transaction-escaping behavior

////////////////////////////////////////////
// end of "boiler-plate" prolog
////////////////////////////////////////////

// do something

// oops, failure, kick it out and try again.
// Just to avoid the whole naked-throw issue, I'll
// do an empty transaction for completeness.  The
// "transaction {}" really shouldn't be necessary.
transaction 
{
} if (problem) throw(oops);

// do something else

////////////////////////////////////////////
// beginning of "boiler-plate" epilog
////////////////////////////////////////////
}
catch(oops O)
{
// this handler is purely to catch local exceptions
// and "reflect" them out to a higher transaction
// level so that we get resumed at the higher level.
overall_problem = true;
ignore; // "ignores" the oops, exiting the transaction
// from here, which will immediately trigger the
// biggie that the global handler will use to
// restart the entire thread.
// Note that we will not perform the 
// "// do something else" section.
}
} if (overall_problem) throw(biggie);
}

void main()
{
try
{
beginthread(threadfunc, NULL);
}
catch(oops O)
{
resume;
}
}


Granted, that is not the prettiest code, but it does give Erlang functionality
without changing any of my original proposal (even the implicit rule that
disallowed naked throws).  It also leaves open the ability to have finer grained
resumption capabilities.  Basically, it gives you as much control as you need,
to use however you need it.

Something that has come up both in discussing this with a coworker and (in a
slightly different form) here in the newsgroup, is the method of registering an
exception handler.  I have shown the C++ try/catch method of "registering" an
exception handler.  This may be considered obfuscatory, confusing, or otherwise
annoying.  People may prefer to replace:

try
{
// whatever
}
catch (exception E)
{
handle_exceptions(E);
}

with:

register_handler(exception, handle_exceptions);
// whatever
unregister_handler(exception, handle_exceptions);

I don't really have a major preference.  I was using the try/catch syntax
because I figured a lot of readers would be familiar with the C++ exception
system, and I wanted to focus on what I was adding to it
(transactions/resume/ignore) rather than on simply changing the syntax of
handler registration.

Again, thanks for reading and commenting.  I do actually want to hear peoples'
thoughts about this.  If I ever come across as pushy, I hope you will forgive me
-- I usually end up debating this sort of thing with someone who argues and
requires considerable forcefulness on my part.

Mac

Look at Erlang (erlang.org) if you are interested in handling resumption well.
They don't do it in the exception handler; they let the thread crash and a
monitor thread (that receives notifications on exceptions in another thread)
recreates it.  I think this is the better approach.
You could create an equivalent system with my approach (or with a minor extension of my approach -- see below). If your entire thread function is a transaction, a simple global handler could take the place of your monitor thread. An exception in a thread would then throw to the global handler, which would resume back at the beginning of the thread, "recreating" it. Technically this would require breaking an implicit rule (which I didn't specify in the original post, so I'm not sure how bad it is to break it...). The system I originally suggested would limit "throw" to the conditional end of a transaction only (only valid as part of "transaction{}if () throw()"). To make the Erlang-emulator from the previous paragraph, you would need the ability to throw without a tied in transaction: void threadfunc(void * context) { transaction { // do something // oops, failure, kick it out and try again if (problem) throw(oops); // do something else } if (overall_problem) throw(biggie); } void main() { try { beginthread(threadfunc, NULL); } catch(oops O) { resume; } } The "oops" exception that was thrown naked would be caught by the "catch(oops O)". The "resume" in that handler would resume to the beginning of the currently active transaction (or to the closest open transaction to the point of the throw -- multithreading makes the simple stack approach infeasible (*1)), which would restart the thread function from the beginning, just like Erlang. I'm sure there are lots of issues I am overlooking, which is why I wanted to post this and see what other people think. I appreciate any feedback, especially from those of you who have tried to use exceptions and realized just how limiting they are in all these other languages. Exceptions and multithreading are funky all the way around, and I need to think about that more. The "naked throw" issue is another thing I overlooked. I had considered it and was originally thinking that it would be equivalent to an empty transaction, or a non-resumable exception. Non-resumable exceptions would cause any handler that tried to resume to rethrow instead. Arguably, this isn't even special handling: logically, you would let the resume go back to the throw (empty transaction), which would throw again (no conditional) which would go to the next higher handler (see original post). This is just what rethrow would do, so it is consistent. Of course, that leaves us with no way to do the Erlang behavior, unless we have a way to break out of a "transaction level", like "break" breaks out of scoping levels. I am definitely still thinking about it, and will probably attempt to roll something like this into my "fake exceptions" system that I developed for CE (Microsoft's C++ compiler for CE does not support exceptions or RTTI, which makes life somewhat entertaining some days...). Having recently re-examined the ANSI specs on setjmp/longjmp, I am once again unimpressed by them. Local automatic (in the C sense, not the newly forming D raii sense) variables can have undefined values after you return to a context through longjmp. This makes the system somewhat less than useful for making reusable code. Their answer is that local variables that you need should be flagged as volatile, but I can't do that to the application's functions from inside my reusable library... I would like to figure out some way to do "finally" from C++ codespace as well, and I've gotten close, but there are some niggly details that make it rather surprising to handle. Anyway, when that gets done and my coworkers and I get a chance to start using it, we will be able to give a better feel for how useful transaction based programming is. (Transaction based database stuff is really useful, so I would expect transaction based programming to have at least moderate usefulness, as long as it is done right) Mac (*1) Multithreading and transaction stacks: Basically this means that each thread needs its own transaction stack (or transactions in the global stack need a thread ID so that you can work with it as an interleaved stack), and that throw will have to extract this information from the thread-local or interleaved stack and include it in the exception object. And you probably want a built in synchronization for your handlers, since they could end up trying to handle exceptions from multiple threads simultaneously. This is all much easier if thread creation and management is part of the language, like in D, instead of sitting in a library like in C/C++. I'm sure there are a ton of other issues that haven't occurred to me yet. I've only been thinking about this kind of solution off and on for around a day...
Sep 17 2002
parent reply Joe Battelle <Joe_member pathlink.com> writes:
I would first like to state that killing and restarting an entire thread seems
to be an extremely large grain solution (and rather draconian).
It works in Erlang because threads are so lightweight/finegrained and they are the main abstraction mechanism (instead of classes). I was just pointing out Erlang because I think it's an interesting approach to reliability/exceptions. Also in Erlang, the preferred approach is that all exceptions _should_ be handled in a draconian fashion and it is preferred to let the thread crash. I realize you provided for the fixup up code, but the problem is that putting a system back into a resumable state after an exception is often very hard. The code to do this ends up having it's fingers into a lot of cookie jars. It would be awful to put this wherever execeptions are caught. It's not much better to try to factor it into reinit methods and call those. I think it's simply much better to admit defeat, tear the object down as gracefully as possible and start the system over. Otherwise you start thinking of exceptions as something not exceptional but commonplace. It is easy to construct examples where the above statements aren't valid. In other words, simple uses of resumption can be argued for. In fact, I used resumption to get around some floating point errors in Digitalk's Smalltalk runtime library. But this was a hack--I was using exceptions for common events that were indeed not exceptional! I shouldn't have had to do it, nor would I need to do that with D where I have all the runtime source. I would be interested in seeing code that relied on resumption that a) wasn't a hack and b) treats exceptions as exceptional cases. I think the main point here is that if it happens regularly enough to warrant a resume, why not handle it in the system proper? [I'm guess I'm so adamant about this because D is growing at an alarming rate and I don't even have my working interfaces yet <g> ]
Sep 17 2002
next sibling parent reply Mac Reiter <Mac_member pathlink.com> writes:
[I'm guess I'm so adamant about this because D is growing at an alarming rate
and I don't even have my working interfaces yet <g> ]
I understand. I'd definitely like to see the interfaces and inheritance thereof working. I'd also like to see inheritance of contracts and invariants implemented. The resumable exception handler is a lower priority issue than either of those for me. It's also lower priority than implementation of whatever form of volatile is eventually chosen. I just wanted to get it out there and see what people thought. I think that there is a need (eventually) for a system like this. Exception handling should be for extremely rare (exceptional, even ;-) cases only, but that doesn't mean they shouldn't be handled. And if you can see a way to handle them without a major restart, it would be nice to have support for doing so. That support will probably usually get used in the "kill it and respawn" method, but in that one case in a hundred where you can do something better, it is quite frustrating to not be able to. I think the example I gave (an optional cache that can give up some memory if needed) is a reasonable one, although it is currently handled in C/C++ through the special purpose new_handler() system. If there aren't any other cases, then maybe we don't need anything other than new_handler(). I just know that I never use C++, VB, or Eiffel exceptions myself, because they are useless to me. The only time I handle them is when a standard library component throws one at me as part of its standard interface (contrary to what "exception" implies) and I have to catch it and deal with it. As I mentioned, I'll probably try to implement this in macros and/or templates for C++ (I've already got the exception part, I just need the resume part), and presumably could do something similar in D (as long as setjmp/longjmp are available). On a somewhat unrelated side-note, if D will provide access to setjmp/longjmp (or equivalent), could it please make stronger statements about what will be preserved than ANSI did for C? In ANSI C I have to assume that all of my local variables may have garbage results after returning to a setjmp point, and the only reason is because the compiler is allowed to optimize register utilization *across* the setjmp() call. That means that after I longjmp and fall out of a setjmp (look up the functions if that didn't make any sense...), the following code may use registers "as if" they still contained the cached versions of local variables. However, setjmp/longjmp do *not* store/restore all register settings, since that is not generally possible on all processors. That means that this "optimized" code can get bogus values and hose itself. All they would have had to do was disallow optimization across a setjmp() call and the problem would go away (setjmp saves the entire stack and longjmp restores the entire stack, so all the local variables are correct in memory -- it is only the incorrectly optimized register copies that can be wrong). But since they didn't require it, setjmp/longjmp are pretty much hosed, because a compiler writer _can_ do something horrible, which means that somewhere, someone will... OK, I also realize the the compiler writer may be peachy, and the optimization could be the result of a later stage optimizing assembler (the work of Satan, in my opinion...). I'm not sure how to handle that. It seems like you should be able to disable optimizations, or flush gunk through the registers after setjmp returns (slows it down slightly, but it would be worth it for a guarantee of correctness), or something else. Personally, I would prefer to avoid optimizing assemblers, but I figure I've lost that battle. If D could require that setjmp/longjmp (or equivalent) had better behavior, then it would be up to compiler implementors to either work it out with their backend assemblers or find another assembler that does what they want (which seems like a reasonable thing to me, but then I'm biased towards tools that do what I tell them to do...) Sorry for the side trip... Mac
Sep 18 2002
parent reply "Walter" <walter digitalmars.com> writes:
Why would you prefer setjmp/longjmp to exceptions?
Sep 23 2002
next sibling parent reply "Sean L. Palmer" <seanpalmer earthlink.net> writes:
Egad.  Masochist?  ;)

Sean

"Walter" <walter digitalmars.com> wrote in message
news:amnk6l$2j71$1 digitaldaemon.com...
 Why would you prefer setjmp/longjmp to exceptions?
Sep 23 2002
parent reply "Walter" <walter digitalmars.com> writes:
"Sean L. Palmer" <seanpalmer earthlink.net> wrote in message
news:amomoe$nft$1 digitaldaemon.com...
 Egad.  Masochist?  ;)

 Sean

 "Walter" <walter digitalmars.com> wrote in message
 news:amnk6l$2j71$1 digitaldaemon.com...
 Why would you prefer setjmp/longjmp to exceptions?
setjmp/longjmp are two of those things best left in the dustbin of history <g>. I remember the long (and acrimonious) debates 12+ years ago about whether C++ should follow the termination or resumption method of handling exceptions. The consensus eventually settled on the termination model, and I think experience has shown it works well enough. It's not an area where I'm well informed, and I'll stick to what I know will work.
Sep 24 2002
parent reply Mac Reiter <Mac_member pathlink.com> writes:
In article <amp30n$15b4$1 digitaldaemon.com>, Walter says...
"Sean L. Palmer" <seanpalmer earthlink.net> wrote in message
news:amomoe$nft$1 digitaldaemon.com...
 Egad.  Masochist?  ;)

 Sean
No, just the opposite actually. I am willing to expend considerable effort in a short, focused development effort that pays considerable dividends to my ease of programming later. setjmp/longjmp feature in some of these focused reused components. Without these components, some of my other programming efforts would have been much more painful, over a much longer time period.
 "Walter" <walter digitalmars.com> wrote in message
 news:amnk6l$2j71$1 digitaldaemon.com...
 Why would you prefer setjmp/longjmp to exceptions?
setjmp/longjmp are two of those things best left in the dustbin of history <g>. I remember the long (and acrimonious) debates 12+ years ago about whether C++ should follow the termination or resumption method of handling exceptions. The consensus eventually settled on the termination model, and I think experience has shown it works well enough. It's not an area where I'm well informed, and I'll stick to what I know will work.
I am a little disturbed that a language that is trying to rid itself of outdated ideas from C/C++ would stick with a decision made over seven years before C++ was standardized. Especially if you consider that some of those decisions might have been performance based. How valid is a performance consideration today that was made based on the behavior of CPUs that were common 12 years ago? 12 years ago, caches were much smaller, 16bit segmented code was the norm, branch prediction was either primitive or non-existent, anonymous registers weren't available to microcode (for that matter, microcode may not have been present), etc. I am also somewhat discouraged that my suggestion keeps getting shot down because resumption in other languages isn't sufficient. I know that resumption in other languages isn't sufficient. That's why I tried to work out a resumption mechanism that actually was useful, and suggest it to a group of people who seem to be interested in making a language full of features that actually are useful. I do realize that I tend to do rather odd things with programming languages, and understand not wanting to spend the considerable effort necessary to support a new paradigm if I am the only person interested (and it would appear that I am, at least amongst the readers of this newsgroup. I know that I have at least two very interested coworkers, but they're unlikely to switch to D anytime soon, so their opinions aren't terribly relevant here...). So how about this: 1. Ignore the resumable exception handling (at least for now ;-), and focus your energies on features you and the other users want and on bug fixes. 2. Leave me access to a setjmp/longjmp that, in some fashion, I can guarantee will return me to the location I stored and with the proper values in all my variables (which basically means any register caches got flushed by the longjmp, I think). If I have access to C's setjmp/longjmp, and if my "externally linked wrapper function" system will provide the no-optimization guarantees I need, then you wouldn't even have to do anything for this step... 3. Finish up whatever you intend to do with raii/auto references and make sure they get cleaned up properly when an exception is thrown. Given those three things (which hopefully don't actually require any code changes that you weren't going to make anyway), I can experiment with my own resumable exception system. I can use setjmp/longjmp for my movements up and down the stack, and then I can use the built-in exceptions when my system reaches a termination/continuation stage. Using the built-in exceptions at that stage will guarantee proper cleanup of raii/auto references and execution of finally clauses (and 'out' contracts? or do exceptions invalidate those?), all of which have to be held off until it is known that the "exception" is not going to be resumed. If I successfully make such a system, and if it shows usefulness either with my home projects or with my (or my coworkers) work projects, then I can come back with examples of how the system simplified the architecture and implementation of some real systems. Then the value can be judged qualitatively, rather than with a great deal of hand-waving, which is all I am currently able to do, since no equivalent resumable exception system exists. Also, my library should be a pretty good starting point for the issues you would have to consider in any compiler implementation you might choose to add. I will point out that the implementation will probably start out as a set of C++ macros and functions. That will increase the likelihood that my coworkers will start playing with the system. Mac
Sep 24 2002
next sibling parent "Walter" <walter digitalmars.com> writes:
"Mac Reiter" <Mac_member pathlink.com> wrote in message
news:amq3fe$2mq4$1 digitaldaemon.com...
 I am a little disturbed that a language that is trying to rid itself of
outdated
 ideas from C/C++ would stick with a decision made over seven years before
C++
 was standardized.
I'll also beg that I'm not well informed about the resumptive model, so I can't argue intelligently on its merits.
 Especially if you consider that some of those decisions might
 have been performance based.  How valid is a performance consideration
today
 that was made based on the behavior of CPUs that were common 12 years ago?
12
 years ago, caches were much smaller, 16bit segmented code was the norm,
branch
 prediction was either primitive or non-existent, anonymous registers
weren't
 available to microcode (for that matter, microcode may not have been
present),
 etc.
I think performance matters a lot, and will continue to matter. CPU improvements in the last 12 years have changed the way code is generated (such as scheduling), but the fundamental issues to writing fast code haven't.
 I am also somewhat discouraged that my suggestion keeps getting shot down
 because resumption in other languages isn't sufficient.  I know that
resumption
 in other languages isn't sufficient.  That's why I tried to work out a
 resumption mechanism that actually was useful, and suggest it to a group
of
 people who seem to be interested in making a language full of features
that
 actually are useful.
Ok.
 I do realize that I tend to do rather odd things with programming
languages, and
 understand not wanting to spend the considerable effort necessary to
support a
 new paradigm if I am the only person interested (and it would appear that
I am,
 at least amongst the readers of this newsgroup.  I know that I have at
least two
 very interested coworkers, but they're unlikely to switch to D anytime
soon, so
 their opinions aren't terribly relevant here...).  So how about this:
One of the goals of D is to make it easy for a correct and efficient compiler to be built. If a feature is hard to implement, coupled with not being widely demanded, realistically puts it further down the list.
 1. Ignore the resumable exception handling (at least for now ;-), and
focus your
 energies on features you and the other users want and on bug fixes.
You're right.
 2. Leave me access to a setjmp/longjmp that, in some fashion, I can
guarantee
 will return me to the location I stored and with the proper values in all
my
 variables (which basically means any register caches got flushed by the
longjmp,
 I think).  If I have access to C's setjmp/longjmp, and if my "externally
linked
 wrapper function" system will provide the no-optimization guarantees I
need,
 then you wouldn't even have to do anything for this step...
I think you can use setjmp/longjmp in D just as you would in C. After all, they share the code generator <g>.
 3. Finish up whatever you intend to do with raii/auto references and make
sure
 they get cleaned up properly when an exception is thrown.
Yes, absolutely.
 Given those three things (which hopefully don't actually require any code
 changes that you weren't going to make anyway), I can experiment with my
own
 resumable exception system.  I can use setjmp/longjmp for my movements up
and
 down the stack, and then I can use the built-in exceptions when my system
 reaches a termination/continuation stage.
Ok.
  Using the built-in exceptions at that
 stage will guarantee proper cleanup of raii/auto references and execution
of
 finally clauses (and 'out' contracts?  or do exceptions invalidate
those?), all
 of which have to be held off until it is known that the "exception" is not
going
 to be resumed.
Out contracts are not executed if an exception is thrown. I should probably document that (!).
 If I successfully make such a system, and if it shows usefulness either
with my
 home projects or with my (or my coworkers) work projects, then I can come
back
 with examples of how the system simplified the architecture and
implementation
 of some real systems.  Then the value can be judged qualitatively, rather
than
 with a great deal of hand-waving, which is all I am currently able to do,
since
 no equivalent resumable exception system exists.  Also, my library should
be a
 pretty good starting point for the issues you would have to consider in
any
 compiler implementation you might choose to add.
Good idea.
 I will point out that the implementation will probably start out as a set
of C++
 macros and functions.  That will increase the likelihood that my coworkers
will
 start playing with the system.
Sep 24 2002
prev sibling parent reply Mark Evans <Mark_member pathlink.com> writes:
Have you looked at Icon's goal-directed evaluation?  It is very well designed.
Walter and you should both take a look at it.  See "coroutines" and "generators"
in the documentation.  (The term "goal directed evaluation" is less frequent.)

Mark


In article <amq3fe$2mq4$1 digitaldaemon.com>, Mac Reiter says...
 setjmp/longjmp feature in some of these focused reused
components.  Without these components, some of my other programming efforts
would have been much more painful, over a much longer time period.
Sep 25 2002
next sibling parent Mark Evans <Mark_member pathlink.com> writes:
Icon uses a success/failure paradigm wherein either runtime condition is
considered normal.  This paradigm is different from an "exception" which by
definition should not happen.  In Icon, one uses many small pass/fail
expressions which, taken as a group, can formulate small algorithms or even
entire programs.

In the abstract, I suppose a language could support both paradigms, though
implementing some of Icon's more advanced features (like goal-directed
evaluation with its use of success/failure) would be lots of extra work.

Mark


In article <amt5mr$jqr$1 digitaldaemon.com>, Mark Evans says...
Have you looked at Icon's goal-directed evaluation?  It is very well designed.
Walter and you should both take a look at it.  See "coroutines" and "generators"
in the documentation.  (The term "goal directed evaluation" is less frequent.)

Mark


In article <amq3fe$2mq4$1 digitaldaemon.com>, Mac Reiter says...
 setjmp/longjmp feature in some of these focused reused
components.  Without these components, some of my other programming efforts
would have been much more painful, over a much longer time period.
Sep 25 2002
prev sibling parent "Walter" <walter digitalmars.com> writes:
"Mark Evans" <Mark_member pathlink.com> wrote in message
news:amt5mr$jqr$1 digitaldaemon.com...
 Have you looked at Icon's goal-directed evaluation?  It is very well
designed.
 Walter and you should both take a look at it.  See "coroutines" and
"generators"
 in the documentation.  (The term "goal directed evaluation" is less
frequent.) I just received a copy of the Icon manual, but I haven't read it yet.
Sep 25 2002
prev sibling parent reply Mac Reiter <Mac_member pathlink.com> writes:
In article <amnk6l$2j71$1 digitaldaemon.com>, Walter says...
Why would you prefer setjmp/longjmp to exceptions?
Sorry for the brevity, but I already typed this once and my machine hosed itself during the send... 1. setjmp/longjmp allow me to make exception capabilities on platforms whose compilers do not support exceptions. 2. exceptions only jump "up". setjmp/longjmp can jump any direction. setjmp/longjmp allow you to create cooperative multithreading systems, which can be useful even when "real" threads are available, because a cooperative multithreading system has lower overhead (no CPU mode switch) and simpler synchronization semantics (all code between "yield()" calls is essentially inside a critical section). Not for everybody, but if you know what you're doing it can be very useful. Such a multithreading library is also cross platform (OS and compiler) portable -- and such libraries have been developed and sold. 3. I can make most of exceptions with setjmp/longjmp (I already have, once). I can even make resumable exceptions with setjmp/longjmp. I cannot make setjmp/longjmp out of exceptions. Of course, setjmp/longjmp would be even more useful if the requirements were more stringent. The actual ANSI requirements leave too much "undefined", as usual... But I think I can get around that by making my own wrapper functions that I put in an external module -- no correct optimizer is going to trust the contents of its general purpose registers after coming back from a function call that resides in a module that isn't known until link time... Mac
Sep 24 2002
parent "Walter" <walter digitalmars.com> writes:
Ok, I understand your reasons now. -Walter
Sep 24 2002
prev sibling parent "Walter" <walter digitalmars.com> writes:
"Joe Battelle" <Joe_member pathlink.com> wrote in message
news:am8jvc$b78$1 digitaldaemon.com...
 [I'm guess I'm so adamant about this because D is growing at an alarming
rate
 and I don't even have my working interfaces yet <g> ]
I haven't forgotten about the interface problem. I'm currently working on the auto support.
Sep 23 2002
prev sibling parent Russ Lewis <spamhole-2001-07-16 deming-os.org> writes:
About the current D syntax: it pretty much is like C++, with the addition of
finally
clauses.  So I guess it's actually like the Java syntax :)



Exception-handling-with-resume can be handled by callback mechanisms:

     In the library code:
        if(UserLandCallback(errorData) == false)
            throw Exception(errorData);    // i.e. the the user couldn't handle
the
error

How about a "errcallback"/"errhandler" syntax:
    void UserFunc()
    {
        try {
            LibraryFunc();
        } errhandler(FooException e) {
            if(things)
                return true;    // error was resolved
            else
                return false;    // error remains
        }
    }

    void LibraryFunc()
    {
        stuff

        if(errorDetected)
            errcallback FooException(errorData);
                // if errhandler returns true, then we just continue happily
                // but if it returns false, we throw the FooException object

        stuff
    }

I don't like the words "errhandler" and "errcallback"...but what does everybody
think of the idea in general?

--
The Villagers are Online! villagersonline.com

.[ (the fox.(quick,brown)) jumped.over(the dog.lazy) ]
.[ (a version.of(English).(precise.more)) is(possible) ]
?[ you want.to(help(develop(it))) ]
Sep 17 2002