D - Exception Handling extension
- Mac Reiter (259/259) Sep 17 2002 One of the things that I like about Visual Basic's and Eiffel's error ha...
- Joe Battelle (10/10) Sep 17 2002 Resumption is one of those ideas that sounds good, but turns out not to ...
- Mac Reiter (122/132) Sep 17 2002 "without fixing things up"? I fully intended to fix things up. That's ...
- Mac Reiter (85/170) Sep 17 2002 As a followup, having thought about the Erlang emulation system some mor...
- Joe Battelle (25/27) Sep 17 2002 It works in Erlang because threads are so lightweight/finegrained and th...
- Mac Reiter (55/57) Sep 18 2002 I understand. I'd definitely like to see the interfaces and inheritance...
- Walter (1/1) Sep 23 2002 Why would you prefer setjmp/longjmp to exceptions?
- Sean L. Palmer (4/5) Sep 23 2002 Egad. Masochist? ;)
- Walter (8/13) Sep 24 2002 setjmp/longjmp are two of those things best left in the dustbin of histo...
- Mac Reiter (58/73) Sep 24 2002 No, just the opposite actually. I am willing to expend considerable eff...
- Walter (56/109) Sep 24 2002 outdated
- Mark Evans (5/8) Sep 25 2002 Have you looked at Icon's goal-directed evaluation? It is very well des...
- Mark Evans (10/18) Sep 25 2002 Icon uses a success/failure paradigm wherein either runtime condition is
- Walter (6/9) Sep 25 2002 designed.
- Mac Reiter (24/25) Sep 24 2002 Sorry for the brevity, but I already typed this once and my machine hose...
- Walter (1/1) Sep 24 2002 Ok, I understand your reasons now. -Walter
- Walter (5/7) Sep 23 2002 rate
- Russ Lewis (35/35) Sep 17 2002 About the current D syntax: it pretty much is like C++, with the additio...
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
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
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
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. MacLook 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
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
[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
Why would you prefer setjmp/longjmp to exceptions?
Sep 23 2002
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
"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...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.Why would you prefer setjmp/longjmp to exceptions?
Sep 24 2002
In article <amp30n$15b4$1 digitaldaemon.com>, Walter says..."Sean L. Palmer" <seanpalmer earthlink.net> wrote in message news:amomoe$nft$1 digitaldaemon.com...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.Egad. Masochist? ;) SeanI 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"Walter" <walter digitalmars.com> wrote in message news:amnk6l$2j71$1 digitaldaemon.com...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.Why would you prefer setjmp/longjmp to exceptions?
Sep 24 2002
"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 ofoutdatedideas from C/C++ would stick with a decision made over seven years beforeC++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 considerationtodaythat was made based on the behavior of CPUs that were common 12 years ago?12years ago, caches were much smaller, 16bit segmented code was the norm,branchprediction was either primitive or non-existent, anonymous registersweren'tavailable to microcode (for that matter, microcode may not have beenpresent),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 thatresumptionin 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 groupofpeople who seem to be interested in making a language full of featuresthatactually are useful.Ok.I do realize that I tend to do rather odd things with programminglanguages, andunderstand not wanting to spend the considerable effort necessary tosupport anew paradigm if I am the only person interested (and it would appear thatI am,at least amongst the readers of this newsgroup. I know that I have atleast twovery interested coworkers, but they're unlikely to switch to D anytimesoon, sotheir 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 ;-), andfocus yourenergies 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 canguaranteewill return me to the location I stored and with the proper values in allmyvariables (which basically means any register caches got flushed by thelongjmp,I think). If I have access to C's setjmp/longjmp, and if my "externallylinkedwrapper function" system will provide the no-optimization guarantees Ineed,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 makesurethey 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 myownresumable exception system. I can use setjmp/longjmp for my movements upanddown 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 executionoffinally clauses (and 'out' contracts? or do exceptions invalidatethose?), allof which have to be held off until it is known that the "exception" is notgoingto 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 eitherwith myhome projects or with my (or my coworkers) work projects, then I can comebackwith examples of how the system simplified the architecture andimplementationof some real systems. Then the value can be judged qualitatively, ratherthanwith a great deal of hand-waving, which is all I am currently able to do,sinceno equivalent resumable exception system exists. Also, my library shouldbe apretty good starting point for the issues you would have to consider inanycompiler implementation you might choose to add.Good idea.I will point out that the implementation will probably start out as a setof C++macros and functions. That will increase the likelihood that my coworkerswillstart playing with the system.
Sep 24 2002
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
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
"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 welldesigned.Walter and you should both take a look at it. See "coroutines" and"generators"in the documentation. (The term "goal directed evaluation" is lessfrequent.) I just received a copy of the Icon manual, but I haven't read it yet.
Sep 25 2002
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
"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 alarmingrateand 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
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