September 13, 2004 Re: Exception hierarchy refactoring | ||||
---|---|---|---|---|
| ||||
Posted in reply to Regan Heath | Regan Heath wrote: > On Sun, 12 Sep 2004 11:57:09 -0400, Ben Hinkle <bhinkle4@juno.com> wrote: >>> I would have said 'OutOfMemory' was an error... however as per Matthew's request lets not debate this here :) >> >> yeah - that could be. I just thought it should be an Exception because >> user >> code should be given a chance to free up cached objects that are sitting >> around in object pools and such. To abort the program because someone >> tried >> to allocate a massive object seems extreme. > > I agree. So are you also suggesting that an Error is uncatchable? Anything is catchable according to the language spec. The code try { ... } catch (Object e) { ... } will catch anything. The question is what should be caught when people are lazy and say try { ... } catch (Exception e) { ... } Should OutOfMemory be caught? Maybe, maybe not. What I would really like is the ability to register a delegate with the GC so that when it can't allocate any more memory it calls the delegate(s) to make a last-ditch effort to free up some memory and try the allocation again and if that fails then throw the exception. If the GC did that then I'd say go ahead and make OutOfMemory an Error because at that point there really isn't any memory left. > This needs to be decided upon and well defined. > > Regan > |
September 13, 2004 Re: Exception hierarchy refactoring | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ben Hinkle | On Sun, 12 Sep 2004 21:50:52 -0400, Ben Hinkle <bhinkle4@juno.com> wrote: > Regan Heath wrote: > >> On Sun, 12 Sep 2004 11:57:09 -0400, Ben Hinkle <bhinkle4@juno.com> wrote: >>>> I would have said 'OutOfMemory' was an error... however as per Matthew's >>>> request lets not debate this here :) >>> >>> yeah - that could be. I just thought it should be an Exception because >>> user >>> code should be given a chance to free up cached objects that are sitting >>> around in object pools and such. To abort the program because someone >>> tried >>> to allocate a massive object seems extreme. >> >> I agree. So are you also suggesting that an Error is uncatchable? > > Anything is catchable according to the language spec. The code > try { > ... > } catch (Object e) { > ... > } > will catch anything. Indeed. IIRC Matthew once suggested that Error should be uncatchable, I was wondering if anyone else felt that way too. My reservation against it is: What if a library is throwing an error (which should have perhaps been an exception), if you cannot catch it, and your application must not simply abort, you cannot use that library. > The question is what should be caught when people are > lazy and say > try { > ... > } catch (Exception e) { > ... > } > Should OutOfMemory be caught? Maybe, maybe not. I think it depends on the application in question. > What I would really like is the ability to register a delegate with the GC so that when it can't > allocate any more memory it calls the delegate(s) to make a last-ditch > effort to free up some memory and try the allocation again and if that > fails then throw the exception. If the GC did that then I'd say go ahead > and make OutOfMemory an Error because at that point there really isn't any memory left. Yes, that would be nice. Regan -- Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/ |
September 13, 2004 Re: Exception hierarchy refactoring | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ben Hinkle | Ben Hinkle wrote:
>
>>Ben, when you said "The only Error subclasses should be AssertError,
>>SwitchError" did you mean?
>> "The only Error subclasses (defined in std.exception) should be
>>AssertError, SwitchError"
>>or
>> "The only Error subclasses (that should ever exist) should be
>>AssertError, SwitchError"
>>?
>
> I meant the first one. Users can define Errors - though I can't think of any
> off the top of my head.
They will probably mostly be platform-dependent. Something that results in memory corruption would be an Error, possibly some sort of halt instruction, etc.
Sean
|
September 13, 2004 Re: Exception hierarchy refactoring | ||||
---|---|---|---|---|
| ||||
Posted in reply to Regan Heath | Regan Heath wrote: > On Sun, 12 Sep 2004 21:50:52 -0400, Ben Hinkle <bhinkle4@juno.com> wrote: > >> Anything is catchable according to the language spec. The code >> try { >> ... >> } catch (Object e) { >> ... >> } >> will catch anything. > > Indeed. IIRC Matthew once suggested that Error should be uncatchable, I was wondering if anyone else felt that way too. I think Matthew said Errors should be unrecoverable, not uncatchable. Either way, that's how I would like to classify Errors. There's no point in throwing something that's uncatchable when you could just terminate the application instead. > My reservation against it is: What if a library is throwing an error (which should have perhaps been an exception), if you cannot catch it, and your application must not simply abort, you cannot use that library. If an Error is thrown, then the application is likely in an undefined state and can not continue anyway. Certainly not producing correct behavior anyway. I would prefer to have the application halt and have a process monitor attempt to restart the application. >> The question is what should be caught when people are >> lazy and say >> try { >> ... >> } catch (Exception e) { >> ... >> } >> Should OutOfMemory be caught? Maybe, maybe not. > > I think it depends on the application in question. > >> What I would really like is the ability to register a delegate with the GC so that when it can't >> allocate any more memory it calls the delegate(s) to make a last-ditch >> effort to free up some memory and try the allocation again and if that >> fails then throw the exception. If the GC did that then I'd say go ahead >> and make OutOfMemory an Error because at that point there really isn't any memory left. > > Yes, that would be nice. Agreed. Though it may still be nice to keep OutOfMemory as an exception. For example, say I'm crazy and write an application like this: # void main() # { # while( true ) # { # try # { # func(); # } # catch( Exception e ) # { # e.print(); # } # } # } # # void func() # { # auto char[] buf; # buf.length( 10000000 ); # func(); # } Eventually the application is either going to run out of stack or heap space and will either explode or signal an OutOfMemory condition. Assuming OutOfMemory is signalled, a delegate may not be able to free up memory for another buffer allocation without some aggressively evil destruction of memory that should be left alone. However if an exception is thrown then the entire heap should be freed up purely as a side-effect of stack unwinding. In this case the application could continue to run just fine, and an Error would be overkill. Sean |
September 13, 2004 Re: Exception hierarchy refactoring | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ben Hinkle | In article <ci2udp$gqr$1@digitaldaemon.com>, Ben Hinkle says... > >The question is what should be caught when people are lazy and say try { > ... >} catch (Exception e) { > ... >} > >Should OutOfMemory be caught? Maybe, maybe not. I don't think the above is lazy. In fact, I think that's the way it should be done (print out the error message and continue running.) An out of memory is usually not recoverable, so it should be an Error I think. But what is and is not recoverable can vary from program to program. If you want to continue on OutOfMemory, just catch it explicitly. > What I would really like is >the ability to register a delegate with the GC so that when it can't allocate any more memory it calls the delegate(s) to make a last-ditch effort to free up some memory and try the allocation again and if that fails then throw the exception. If the GC did that then I'd say go ahead and make OutOfMemory an Error because at that point there really isn't any memory left. GC callback functions is a good idea, IMHO. A callback for before/after garbage collection runs, and maybe a "this object/memory area is about to be destructed/freed" callback. Nick |
September 13, 2004 Re: Exception hierarchy refactoring | ||||
---|---|---|---|---|
| ||||
Posted in reply to Sean Kelly | On Sun, 12 Sep 2004 22:28:35 -0700, Sean Kelly <sean@f4.ca> wrote: > Regan Heath wrote: > >> On Sun, 12 Sep 2004 21:50:52 -0400, Ben Hinkle <bhinkle4@juno.com> wrote: >> >>> Anything is catchable according to the language spec. The code >>> try { >>> ... >>> } catch (Object e) { >>> ... >>> } >>> will catch anything. >> >> Indeed. IIRC Matthew once suggested that Error should be uncatchable, I was wondering if anyone else felt that way too. > > I think Matthew said Errors should be unrecoverable, not uncatchable. Ahh yes, sorry for miss-representing you Matthew. :) > Either way, that's how I would like to classify Errors. There's no point in throwing something that's uncatchable when you could just terminate the application instead. I agree. If you cannot catch it, there is no point in throwing it. >> My reservation against it is: What if a library is throwing an error (which should have perhaps been an exception), if you cannot catch it, and your application must not simply abort, you cannot use that library. > > If an Error is thrown, then the application is likely in an undefined state and can not continue anyway. Certainly not producing correct behavior anyway. I would prefer to have the application halt and have a process monitor attempt to restart the application. Agreed, provided the Error really does mean it's un-recoverable. My example was trying to suggest a miss classified error, or rather one that is not un-recoverable for this particular application. So the defintion of 'Error' is 'something that is un-recoverable' or something similar, I think we need a solid definition of this. >>> The question is what should be caught when people are >>> lazy and say >>> try { >>> ... >>> } catch (Exception e) { >>> ... >>> } >>> Should OutOfMemory be caught? Maybe, maybe not. >> >> I think it depends on the application in question. >> >>> What I would really like is the ability to register a delegate with the GC so that when it can't >>> allocate any more memory it calls the delegate(s) to make a last-ditch >>> effort to free up some memory and try the allocation again and if that >>> fails then throw the exception. If the GC did that then I'd say go ahead >>> and make OutOfMemory an Error because at that point there really isn't any memory left. >> >> Yes, that would be nice. > > Agreed. Though it may still be nice to keep OutOfMemory as an exception. For example, say I'm crazy and write an application like this: > > # void main() > # { > # while( true ) > # { > # try > # { > # func(); > # } > # catch( Exception e ) > # { > # e.print(); > # } > # } > # } > # > # void func() > # { > # auto char[] buf; > # buf.length( 10000000 ); > # func(); > # } > > Eventually the application is either going to run out of stack or heap space and will either explode or signal an OutOfMemory condition. Assuming OutOfMemory is signalled, a delegate may not be able to free up memory for another buffer allocation without some aggressively evil destruction of memory that should be left alone. However if an exception is thrown then the entire heap should be freed up purely as a side-effect of stack unwinding. In this case the application could continue to run just fine, and an Error would be overkill. True, you've convinced me. I don't think OutOfMemory is un-recoverable. Regan -- Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/ |
September 13, 2004 Re: Exception hierarchy refactoring | ||||
---|---|---|---|---|
| ||||
Posted in reply to Nick | Nick wrote: > In article <ci2udp$gqr$1@digitaldaemon.com>, Ben Hinkle says... >> >>The question is what should be caught when people are lazy and say try { >> ... >>} catch (Exception e) { >> ... >>} >> >>Should OutOfMemory be caught? Maybe, maybe not. > > I don't think the above is lazy. In fact, I think that's the way it should be done (print out the error message and continue running.) An out of memory is usually not recoverable, so it should be an Error I think. But what is and is not recoverable can vary from program to program. If you want to continue on OutOfMemory, just catch it explicitly. Catching all exceptions is what I meant by lazy. Usually one should catch the smallest collection of exceptions as possible. Eg: catch StreamException or ThreadException etc. Catching all exceptions is a very wide net. >> What I would really like is >>the ability to register a delegate with the GC so that when it can't allocate any more memory it calls the delegate(s) to make a last-ditch effort to free up some memory and try the allocation again and if that fails then throw the exception. If the GC did that then I'd say go ahead and make OutOfMemory an Error because at that point there really isn't any memory left. > > GC callback functions is a good idea, IMHO. A callback for before/after garbage collection runs, and maybe a "this object/memory area is about to be destructed/freed" callback. I'm curious, what use-case do you have in mind for a per-object callback? I could imagine a post-destructor callback so that AJ (for instance) could install a memory-wiper routine. > Nick |
September 14, 2004 Re: Exception hierarchy refactoring | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ben Hinkle | In article <ci5a1k$1948$1@digitaldaemon.com>, Ben Hinkle says... > >Nick wrote: >> I don't think the above is lazy. In fact, I think that's the way it should be done (print out the error message and continue running.) An out of memory is usually not recoverable, so it should be an Error I think. But what is and is not recoverable can vary from program to program. If you want to continue on OutOfMemory, just catch it explicitly. > >Catching all exceptions is what I meant by lazy. Usually one should catch the smallest collection of exceptions as possible. Eg: catch StreamException or ThreadException etc. Catching all exceptions is a very wide net. For many cases I agree with you, but in some cases you will want to catch all exceptions at one single point, without necessarily knowing what kind of exceptions they are. For example if you are calling external library functions. >> GC callback functions is a good idea, IMHO. A callback for before/after garbage collection runs, and maybe a "this object/memory area is about to be destructed/freed" callback. > >I'm curious, what use-case do you have in mind for a per-object callback? I could imagine a post-destructor callback so that AJ (for instance) could install a memory-wiper routine. I had an example in mind, but it isn't a very good example. I thought it could be used instead of finalizers when you have objects which depend on each other. For example: # class File ... # # class BufferedFile # { # File f; # # this() # { # f = new File(whatever); # gc.callBeforeDestruction(f, &callback); # gc.callBeforeDestruction(this, &callback); # } # # callback() # { # // Make sure we are only called once # gc.removeCallback(&callback); # # // Flush buffer while both objects are guaranteed to still be alive. # flush(); # } # # } So now both objects are guaranteed to be alive when BufferedFile.flush() is called. But since you are not supposed to rely on object destruction, you are not guaranteed that callback() gets called at all. Therefore it is not a very usable example. Nick |
September 15, 2004 Re: Exception hierarchy refactoring | ||||
---|---|---|---|---|
| ||||
Posted in reply to Matthew | "Matthew" <admin.hat@stlsoft.dot.org> wrote in news:chrebn$12f7$1@digitaldaemon.com: > I've persuaded big-W that the exception hierarchy refactoring can wait no longer, but to do so I've had to volunteer to marshal the efforts. > > My plan is as follows: > > 1. Solicit *on-topic* criticisms from the newsgroup > > 2. Put on my code reviewer's hat, and do a detailed analysis and criticism of the current situation. I'll do this *before* I read any of these criticisms, so as to be unbiased in the review. This means you've a week or so before I'll be digesting and collating any responses from the community, so please take your time to provide considered comments. > >[snip] I propose this (over-engineered) java-like exception hierarchy: Object | |Throwable | |Error | | | |TooManyThreadsError | |OutOfMemoryError | |AssertionError | | | |ArrayBoundsError | |AssertError | |SwitchError | |Exception | |CheckedException | | | |FileException | ... |UncheckedException | |FormatException ... What's a Throwable? ------------------- By convention all exceptions derive from Throwable. It's still possible to throw any object, though. This way you can create your own exception hierarchy if needed. // Throwable contains an exception message and keeps track of // nested exceptions. class Throwable { private char[] msg; private Throwable next; this(char[] msg="", Throwable next=null); // returns a (user) message that describes the cause of the exception; // returns an empty string, if no message is available char[] getMessage(); // returns the nested exception or null if there is none Throwable getNext(); // returns a (somewhat cryptic) string char[] toString() { char[]rs=this.classinfo.name~": "~this.getMessage(); if (next) rs~="\n"~next.toString(); return rs; } } What are Errors? ---------------- Errors are thrown for exceptions that *might* require drastic means to recover from; like terminating the process or the thread, switching over to a backup system, rebooting computer (for Win9x), etc. When should an exception be derived from Error? 1.) when there are no guarantees when the exception occurs: At minimum a) there must be no resource leaks b) the destructor of an instance must be safely callable if an exception is thrown. If these minimum guarantees cannot be met, the exception is an Error. Furthermore, classes often have stronger guarantees, e.g. instance remains in a valid state if an exception is thrown. For cases that don't permit these stronger guarantees, an Error should be thrown instead of an Exception. 2.) when the exception might not be thrown in release builds: Although these exceptions could be safely catched in debug builds, the conditions that caused them would likely cause havoc for release builds. What about out of memory errors? ------------------------------- I argue that out of memory conditions are Errors, since in the days of virtual memory, most programmers assume that there is always enough memory available, unless a very large amount of memory is requested. Consequently, the vast majority of libraries don't come with exception guarantees when memory is running low. For the same reasons std.thread.Thread.start() should throw a TooManyThreadsError exception instead of a TooManyThreadsException. Why AssertionError? ------------------- Switch errors, array index errors and failed assert statements, mean all but one thing: A bug was revealed. // AssertionError doesn't provide an exception message, but stores // the location where the exception was thrown. class AssertionError : Error { private uint linnum; private char[] filename; this(char[] filename, uint linnum) { super(); this.filename=filename this.linnum=linnum } uint getLinenum(); char[] getFilename(); char[] toString() { return this.classinfo.name~" exception source: " ~filename~" ("~toString(linnum)~")"; } } Speaking of AssertionErrors, it would be useful if DMD offered an option "-spoiledprogrammer" for spoiled programmers. If this option is enabled, AssertionErrors would additionally contain * the function name for all AssertionErrors * the expression of an assert statement * the index that causes an ArrayBoundsError * the actual value that causes a SwitchError What are Exceptions? -------------------- Exceptions are thrown for conditions that leave the process in a defined state. Since Exceptions are thrown for predictable situations, it is usually easy to safely recover from the exceptional situation. We don't need no stinking checked exceptions! --------------------------------------------- Agreed, no checked exceptions. While the constant nagging of the Java compiler about undeclared checked exceptions isn't particularly useful, the concept behind them is. So, borrow the concept from Java, but not it's implementation. By convention, CheckedExceptions are thrown for errors that the programmer should carefully think about. Exceptions that derive from CheckedException * cannot be avoided by the programmer. For instance, opening a file, might fail: Even if you ensure that the file exists before opening the file, the file might have been deleted by the time it is actually opened! * might occur during the intended operation of a programm; they do not necessarily indicate bugs. * should be documented for all functions in the call hierarchy that might throw them When to use UncheckedExceptions ------------------------------- UncheckedExceptions are thrown for errors that don't require the programmer's attention, since these errors *usually* never happen. UncheckedExceptions usually indicate that a function is used in the wrong way by the caller. UncheckedExceptions * can be avoided for most use-cases. E.g. std.format.doFormat(...) throws a FormatException if the passed arguments do not match the format pattern. The exception can be avoided by passing a correct format pattern string. * usually indicate bugs, but not always * might not be documented in the function contract. Of course, it's good practice to do so, especially for functions that directly throw UncheckedExceptions. Errors vs. UncheckedExceptions ------------------------------ UncheckedExceptions * are part of the function's contract. As long as the contract isn't changed, the caller can rely on UncheckedExceptions beeing thrown. * leave the process in a defined state (exception guarantees like: no resource leaks, object instances remain valid, etc. permit that the the process can safely recover from the exception ) Errors * are not necessarily part of the function contract: Checks that throw Errors, might be absent in release builds or future versions of the function. * might leave the programm in an undefined state; should the Error be ignored, unpredictable follow-up errors might occur. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ I also like Ben's idea to include a bunch of generic exception classes in the standard library. This would be particularly handy for quick and dirty programming. Farmer. After writing this post, I recognized that the base class Throwable is a bit pointless for runtime generated Errors. Since these Errors neither have a user message nor a nested exception. So here's another, less java-like exception hierarchy. The presented guidelines for using Exceptions/Errors remain valid for this hierarchy. Object | |TooManyThreads |OutOfMemory |AssertionError | |ArrayBoundsError | |AssertError | |SwitchError | |Exception | |CheckedException // should be catched | | | |FileException | ... |UncheckedException // safe to catch | | | |FormatException | ... | |UnrecoverableError // unsafe to catch | |NullArgumentError ... |
September 15, 2004 Re: Exception hierarchy refactoring | ||||
---|---|---|---|---|
| ||||
Posted in reply to Farmer | In article <Xns8FB0B405185D2itsFarmer@63.105.9.61>, Farmer says... > >I propose this (over-engineered) java-like exception hierarchy: > >Object >| >|Throwable > | > |Error > | | > | |TooManyThreadsError > | |OutOfMemoryError > | |AssertionError > | | > | |ArrayBoundsError > | |AssertError > | |SwitchError > | > |Exception > | > |CheckedException > | | > | |FileException > | ... > |UncheckedException > | > |FormatException > ... .. >What are Errors? >---------------- > >Errors are thrown for exceptions that *might* require drastic means to recover from; like terminating the process or the thread, switching over to a backup system, rebooting computer (for Win9x), etc. > >When should an exception be derived from Error? > >1.) when there are no guarantees when the exception occurs: > At minimum > a) there must be no resource leaks > b) the destructor of an instance must be safely callable >if an exception is thrown. >If these minimum guarantees cannot be met, the exception is an Error. In this case, I would argue that TooManyThreadsError and OutOfMemoryError should be derived from Exception, since they both meet these requirements. >Furthermore, classes often have stronger guarantees, e.g. instance remains in a valid state if an exception is thrown. For cases that don't permit these stronger guarantees, an Error should be thrown instead of an Exception. Could you go into this a bit more? Are you just saying that, basically, if a class invariant is violated then an Error should be thrown? If so, I *think* I agree but I'll have to think about it. >2.) when the exception might not be thrown in release builds: >Although these exceptions could be safely catched in debug builds, the >conditions that caused them would likely cause havoc for release builds. I'm having trouble thinking of an exception that might be recoverable in a debug build but not in a release build. Or do you mean only that they could be thrown and caught safely? And if this is the case, then I would argue that they shouldn't be Errors *or* Exceptions, but rather that the application should terminate with a core dump. >What about out of memory errors? >------------------------------- > >I argue that out of memory conditions are Errors, since in the days of virtual memory, most programmers assume that there is always enough memory available, unless a very large amount of memory is requested. For a specific class of applications, yes. But what about systems programming, embedded systems, etc? I might argue that such applications should be designed such that an out of memory condition should never occur, but they certainly aren't designed as if memory were unlimited. I think OutOfMemory should be an Exception because it's a situation that some applications may want to recover from, not because that is expected of all applications. >Consequently, the vast majority of libraries don't come with exception guarantees when memory is running low. I would argue that any library that is not exception safe is incorrect. It shouldn't matter what kind of exception it is. So I assume that any library that doesn't document otherwise at least provides the basic guarantee. >For the same reasons std.thread.Thread.start() should throw a TooManyThreadsError exception instead of a TooManyThreadsException. For the same reason, I think this should be an exception :) The user should be able to choose whether this constitutes an application failure. As for checked vs. unchecked exceptions, I'm not sure I understand the difference. Are you saying that checked exceptions should be handled explicitly while unchecked exceptions can be caught via the Exception base class, the error reported, and that's it? Obviously, all exceptions must be handled somewhere or the application will terminate. Sean |
Copyright © 1999-2021 by the D Language Foundation