Thread overview | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
September 17, 2002 Exception Handling extension | ||||
---|---|---|---|---|
| ||||
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 |
September 17, 2002 Re: Exception Handling extension | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mac Reiter | 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. |
September 17, 2002 Re: Exception Handling extension | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mac Reiter | 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))) ] |
September 17, 2002 Re: Exception Handling extension | ||||
---|---|---|---|---|
| ||||
Posted in reply to Joe Battelle | 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... |
September 17, 2002 Re: Exception Handling extension | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mac Reiter | 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... > > |
September 18, 2002 Re: Exception Handling extension | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mac Reiter | >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> ]
|
September 18, 2002 Re: Exception Handling extension | ||||
---|---|---|---|---|
| ||||
Posted in reply to Joe Battelle | >[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
|
September 23, 2002 Re: Exception Handling extension | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mac Reiter | Why would you prefer setjmp/longjmp to exceptions? |
September 23, 2002 Re: Exception Handling extension | ||||
---|---|---|---|---|
| ||||
Posted in reply to Joe Battelle | "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. |
September 24, 2002 Re: Exception Handling extension | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | Egad. Masochist? ;) Sean "Walter" <walter@digitalmars.com> wrote in message news:amnk6l$2j71$1@digitaldaemon.com... > Why would you prefer setjmp/longjmp to exceptions? |
Copyright © 1999-2021 by the D Language Foundation