www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Java also has chained exceptions, done manually

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.com> writes:
In an earlier post, Don Clugston wrote:

 When I originally implemented this, I discovered that the idea of
 "chained exceptions" was hopeless naive. The idea was that while
 processing one exception, if you encounter a second one, and you
 chain them together. Then you get a third, fourth, etc.
 
 The problem is that it's much more complicated than that. Each of the
 exceptions can be a chain of exceptions themselves. This means that
 you don't end up with a chain of exceptions, but rather a tree of
 exceptions. That's why there are those really nasty test cases in the
 test suite.
 
 The examples in the test suite are very difficult to understand if
 you expect it to be a simple chain!
 
 On the one hand, I was very proud that I was able to work out the
 barely-documented behaviour of Windows SEH, and it was really
 thorough. In the initial implementation, all the complexity was
 covered. It wasn't the bugfix-driven-development which dmd usually
 operates under <g>.
 
 But on the other hand, once you can see all of the complexity,
 exception chaining becomes much less convincing as a concept. Sure,
 the full exception tree is available in the final exception which you
 catch. But, is it of any use? I doubt it very much. It's pretty
 clearly a nett loss to the language, it increases complexity with
 negligible benefit. Fortunately in this case, the cost isn't really
 high.
First off, there's no tree of exceptions simply because... well it's not there. There is on field "next", not two fields "left" and "right". It's a linear list, not a tree. During construction there might be the situation whereby two lists need to be merged. But they will be merged by necessity into a singly-linked list, not a tree, because we have no structural representation of a tree. (As an aside, it does seem we could allow some weird cases where people rethrow some exception down the chain, thus creating loops. Hopefully that's handled properly.) Second, it does pay to keep abreast other languages. I had no idea (and am quite ashamed of it) that Java also has chained exceptions: https://www.geeksforgeeks.org/chained-exceptions-java/ They implement them manually, i.e. the user who throws a new exception would need to pass the existing exception (or exception chain) as an argument to the new exception's constructor. Otherwise, an exception thrown from a catch/finally block obliterates the existing exception and replaces it with the new one: https://stackoverflow.com/questions/3779285/exception-thrown-in-catch-and-finally-clause So chaining exceptions in Java is a nice complementary mechanism to compensate for that loss in information: when you throw, you have the chance to chain the current exception so it doesn't get ignored. Because of that, D's chained exceptions mechanism can be seen as an automated way of doing "the right thing" in Java. We should study similarities and distinctions with Java's mechanism and discuss them in our documentation. Andrei
Sep 06 2018
next sibling parent reply Neia Neutuladh <neia ikeran.org> writes:
On Thursday, 6 September 2018 at 14:39:12 UTC, Andrei 
Alexandrescu wrote:
 First off, there's no tree of exceptions simply because... well 
 it's not there. There is on field "next", not two fields "left" 
 and "right". It's a linear list, not a tree. During 
 construction there might be the situation whereby two lists 
 need to be merged. But they will be merged by necessity into a 
 singly-linked list, not a tree, because we have no structural 
 representation of a tree.
The runtime appends any exception raised by a `scope (exit|failure)` statement to the next list of an exception already being raised. So if I write: scope (exit) throw new Exception("scope 1"); scope (exit) throw new Exception("scope 2"); throw new Exception("primary"); I get output like: object.Exception scratch.d(21): primary ---------------- ??:? void scratch.throwy() [0x53a74c01] ??:? _Dmain [0x53a74d68] object.Exception scratch.d(26): scope 2 ---------------- ??:? void scratch.throwy() [0x53a74ce1] ??:? _Dmain [0x53a74d68] object.Exception scratch.d(25): scope 1 ---------------- ??:? void scratch.throwy() [0x53a74d42] ??:? _Dmain [0x53a74d68] Okay, that seems reasonable. But what if each of those `scope(exit)` statements had their own chains? scope (exit) throw new Exception("scope 1", new Exception("cause 1")); scope (exit) throw new Exception("scope 2", new Exception("cause 2")); throw new Exception("primary"); object.Exception scratch.d(8): primary ---------------- ??:? void scratch.throwy() [0x7259caf3] ??:? _Dmain [0x7259cc64] object.Exception scratch.d(7): scope 2 ---------------- ??:? void scratch.throwy() [0x7259cba5] ??:? _Dmain [0x7259cc64] object.Exception scratch.d(7): cause 2 object.Exception scratch.d(6): scope 1 ---------------- ??:? void scratch.throwy() [0x7259cc4c] ??:? _Dmain [0x7259cc64] object.Exception scratch.d(6): cause 1 The actual structure of the exceptions: `primary` has children `scope 2` and `scope 1`; `scope 2` has child `cause 2`; `scope 1` has child `cause 1`. A tree. The encoded structure: a linked list where only the first two positions have any structure-related meaning and the rest are just a sort of mish-mash. This isn't a situation you get in Java because Java doesn't have a way to enqueue multiple independent actions at the end of the same block. You just have try/finally and try(closeable).
 (As an aside, it does seem we could allow some weird cases 
 where people rethrow some exception down the chain, thus 
 creating loops. Hopefully that's handled properly.)
Not if you semi-manually create the loop: auto e = new Exception("root"); scope (exit) throw new Exception("scope 1", e); throw e; Filed as https://issues.dlang.org/show_bug.cgi?id=19231
Sep 06 2018
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 9/6/18 1:13 PM, Neia Neutuladh wrote:
 The actual structure of the exceptions: `primary` has children `scope 2` 
 and `scope 1`; `scope 2` has child `cause 2`; `scope 1` has child `cause 
 1`. A tree.
No, it's a list.
 The encoded structure: a linked list where only the first two positions 
 have any structure-related meaning and the rest are just a sort of 
 mish-mash.
 
 This isn't a situation you get in Java because Java doesn't have a way 
 to enqueue multiple independent actions at the end of the same block. 
 You just have try/finally and try(closeable).
 
 (As an aside, it does seem we could allow some weird cases where 
 people rethrow some exception down the chain, thus creating loops. 
 Hopefully that's handled properly.)
Not if you semi-manually create the loop: auto e = new Exception("root"); scope (exit) throw new Exception("scope 1", e); throw e; Filed as https://issues.dlang.org/show_bug.cgi?id=19231
Thanks! Andrei
Sep 06 2018
parent Neia Neutuladh <neia ikeran.org> writes:
On Thursday, 6 September 2018 at 23:34:20 UTC, Andrei 
Alexandrescu wrote:
 On 9/6/18 1:13 PM, Neia Neutuladh wrote:
 The actual structure of the exceptions: `primary` has children 
 `scope 2` and `scope 1`; `scope 2` has child `cause 2`; `scope 
 1` has child `cause 1`. A tree.
No, it's a list.
What relationship is supposed to be encoded in Throwable.next?
Sep 06 2018
prev sibling next sibling parent reply Don <presthetictelevisions teletubby.medical.com> writes:
On Thursday, 6 September 2018 at 14:39:12 UTC, Andrei 
Alexandrescu wrote:
 In an earlier post, Don Clugston wrote:

 When I originally implemented this, I discovered that the idea 
 of
 "chained exceptions" was hopeless naive. The idea was that 
 while
 processing one exception, if you encounter a second one, and 
 you
 chain them together. Then you get a third, fourth, etc.
 
 The problem is that it's much more complicated than that. Each 
 of the
 exceptions can be a chain of exceptions themselves. This means 
 that
 you don't end up with a chain of exceptions, but rather a tree 
 of
 exceptions. That's why there are those really nasty test cases 
 in the
 test suite.
 
 The examples in the test suite are very difficult to 
 understand if
 you expect it to be a simple chain!
 
 On the one hand, I was very proud that I was able to work out 
 the
 barely-documented behaviour of Windows SEH, and it was really
 thorough. In the initial implementation, all the complexity was
 covered. It wasn't the bugfix-driven-development which dmd 
 usually
 operates under <g>.
 
 But on the other hand, once you can see all of the complexity,
 exception chaining becomes much less convincing as a concept. 
 Sure,
 the full exception tree is available in the final exception 
 which you
 catch. But, is it of any use? I doubt it very much. It's pretty
 clearly a nett loss to the language, it increases complexity 
 with
 negligible benefit. Fortunately in this case, the cost isn't 
 really
 high.
First off, there's no tree of exceptions simply because... well it's not there. There is on field "next", not two fields "left" and "right". It's a linear list, not a tree. During construction there might be the situation whereby two lists need to be merged. But they will be merged by necessity into a singly-linked list, not a tree, because we have no structural representation of a tree.
That's correct, the *end result* is not a tree of exceptions, but the intermediate state is a tree. There can be an arbitrary number of exceptions in flight at any given time. The final exception chain is the result of a depth-first traverse of the call stack. Given an exception chain A->B->C, you don't know if C was thrown while processing B, or while processing A. So the exception chain is inherently ambiguous. My point is that this reduces the appeal of the feature. Note that my message was in response to Walter being confused as to why the tests were so complicated.
 (As an aside, it does seem we could allow some weird cases 
 where people rethrow some exception down the chain, thus 
 creating loops. Hopefully that's handled properly.)
A loop is not possible in ordinary operation, any more than a loop is possible in a depth-first traverse of a tree. But, what I don't know is, what happens if there is a switch to a different fiber inside a `finally` clause? I never considered that. At Sociomantic we have code to deal with exceptions being thrown across context switches. Normally it's a bad idea. However, I don't believe we considered that the exception being thrown across the context switch, might not be the only exception in flight at that moment. Maybe it's perfectly fine. I just don't know.
 Second, it does pay to keep abreast other languages. I had no 
 idea (and am quite ashamed of it) that Java also has chained 
 exceptions:

 https://www.geeksforgeeks.org/chained-exceptions-java/
This is fascinating.
 They implement them manually, i.e. the user who throws a new 
 exception would need to pass the existing exception (or 
 exception chain) as an argument to the new exception's 
 constructor. Otherwise, an exception thrown from a 
 catch/finally block obliterates the existing exception and 
 replaces it with the new one:

 https://stackoverflow.com/questions/3779285/exception-thrown-in-catch-and-finally-clause

 So chaining exceptions in Java is a nice complementary 
 mechanism to compensate for that loss in information: when you 
 throw, you have the chance to chain the current exception so it 
 doesn't get ignored. Because of that, D's chained exceptions 
 mechanism can be seen as an automated way of doing "the right 
 thing" in Java.

 We should study similarities and distinctions with Java's 
 mechanism and discuss them in our documentation.


 Andrei
It's implemented, it works, I was quite proud of having figured it out. Is it actually useful? Dunno. Don.
Sep 06 2018
parent Shachar Shemesh <shachar weka.io> writes:
On 07/09/18 09:42, Don wrote:
 A loop is not possible in ordinary operation, any more than a loop is 
 possible in a depth-first traverse of a tree. But, what I don't know is, 
 what happens if there is a switch to a different fiber inside a 
 `finally` clause?
 I never considered that.
Each fiber maintains its own EH context. In theory (and I will not claim that I actually understand how this construct works), if you switch to a different fiber, you'll be out of the EH handling code until you switch back. It caused quite a bit of headache for Mecca, as we do not switch fibers using the DRuntime mechanism. At the moment, Mecca just copies internal DRuntime implementation details while keeping our fingers tightly crossed that nothing gets borken. I have, fairly high on my to-do list, a task to submit a PR to DRuntime to properly export this interface, thus removing access to private members from Mecca. Such a change would also allow DRuntime to implement the Fiber class somewhere other than core.thread. Shachar
Sep 06 2018
prev sibling next sibling parent Kagamin <spam here.lot> writes:
I think he means colliding exceptions, not chained, their use 
cases are not identical. You can have chaining in any language, 
but it doesn't necessarily happen on collision.
Sep 07 2018
prev sibling parent reply Tobias Mueller <troplin bluewin.ch> writes:
On Thursday, 6 September 2018 at 14:39:12 UTC, Andrei 
Alexandrescu wrote:
 Second, it does pay to keep abreast other languages. I had no 
 idea (and am quite ashamed of it) that Java also has chained 
 exceptions:

 https://www.geeksforgeeks.org/chained-exceptions-java/
Now I'm surprised... is there a language (except C++) that supports exceptions that does _not_ have it? Java: getCause() PHP: getPrevious() ...
 They implement them manually, i.e. the user who throws a new 
 exception would need to pass the existing exception (or 
 exception chain) as an argument to the new exception's 
 constructor. Otherwise, an exception thrown from a 
 catch/finally block obliterates the existing exception and 
 replaces it with the new one:

 https://stackoverflow.com/questions/3779285/exception-thrown-in-catch-and-finally-clause

 So chaining exceptions in Java is a nice complementary 
 mechanism to compensate for that loss in information: when you 
 throw, you have the chance to chain the current exception so it 
 doesn't get ignored. Because of that, D's chained exceptions 
 mechanism can be seen as an automated way of doing "the right 
 thing" in Java.

 We should study similarities and distinctions with Java's 
 mechanism and discuss them in our documentation.
I think chained exception in D is semantically very different from other languages like Java. do with saving exceptions that are thrown in finally blocks. Instead the chain is used for a layer model. The "outer" exception is the higher level exception and the "inner" exception is the lower level cause of the higher level exception. Often a low level exception is quite meaningless when thrown over multiple levels in the call stack. For example, imagine a program that uses a configuration file. While parsing the configuration, a misformatted number is encountered. If the NumberFormatException is propagated to the top level, it is basically useless for programmatic error handling. This is much better: ConfigurationException -> JsonParsingException -> NumberFormatException The higher level exceptions are therefore directly caused by the lower level exception and always thrown _directly_ in the catch block where the lower level exception is catched and readily available. Not in a finally block and not indirectly in a function called from the catch block. Manually passing the inner exception to the constructor of the outer exception is trivial and IMO the right thing, because it's probably not always wanted. Now in D, chained exceptions work differently. The first exception that is thrown is deemed the "important" exception and later exceptions are simply chained to it. A direct semantic connection between the exceptions is not possible at all, since the later exception has no knowledge about the first exception at all. It also means that the order of the exceptions is reversed. The first exception is the "outer" exception which is the exact opposite of how it is meant in Java. When handling an exception, it's not clear at all what those additional exceptions mean and how they should be handled. --- Tobias
Sep 07 2018
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 9/7/18 7:15 PM, Tobias Mueller wrote:
 On Thursday, 6 September 2018 at 14:39:12 UTC, Andrei Alexandrescu wrote:
 Second, it does pay to keep abreast other languages. I had no idea 
 (and am quite ashamed of it) that Java also has chained exceptions:

 https://www.geeksforgeeks.org/chained-exceptions-java/
Now I'm surprised... is there a language (except C++) that supports exceptions that does _not_ have it? Java: getCause() PHP: getPrevious() ...
David Nadlinger wrote that Python also has them.
 I think chained exception in D is semantically very different from other 
 languages like Java.
 

 saving exceptions that are thrown in finally blocks.
 Instead the chain is used for a layer model. The "outer" exception is 
 the higher level exception and the "inner" exception is the lower level 
 cause of the higher level exception.
 
 Often a low level exception is quite meaningless when thrown over 
 multiple levels in the call stack.
 For example, imagine a program that uses a configuration file. While 
 parsing the configuration, a misformatted number is encountered.
 If the NumberFormatException is propagated to the top level, it is 
 basically useless for programmatic error handling.
 This is much better:
 ConfigurationException -> JsonParsingException -> NumberFormatException
 
 The higher level exceptions are therefore directly caused by the lower 
 level exception and always thrown _directly_ in the catch block where 
 the lower level exception is catched and readily available. Not in a 
 finally block and not indirectly in a function called from the catch block.
 Manually passing the inner exception to the constructor of the outer 
 exception is trivial and IMO the right thing, because it's probably not 
 always wanted.
 
 Now in D, chained exceptions work differently.
 The first exception that is thrown is deemed the "important" exception 
 and later exceptions are simply chained to it. A direct semantic 
 connection between the exceptions is not possible at all, since the 
 later exception has no knowledge about the first exception at all.
 It also means that the order of the exceptions is reversed. The first 
 exception is the "outer" exception which is the exact opposite of how it 
 is meant in Java.
 
 When handling an exception, it's not clear at all what those additional 
 exceptions mean and how they should be handled.
 

Well indeed the upper you are on the stack, the more likely you are in a high-level function. It's a good heuristic. Sadly we cannot change chaining semantics now, but it does stand to reason to print the last exception in a chain, instead of the head, as the most relevant cause. Guess we'd do good to have such functionality in the stdlib. Andrei
Sep 08 2018
parent Neia Neutuladh <neia ikeran.org> writes:
On Sunday, 9 September 2018 at 01:09:24 UTC, Andrei Alexandrescu 
wrote:
 it does stand to reason to print the last exception in a chain, 
 instead of the head, as the most relevant cause. Guess we'd do 
 good to have such functionality in the stdlib.
So given code like: scope (exit) throw new Exception("cleanup failed"); throw new Exception("main operation failed"); You would prefer that this show the "cleanup failed" exception more prominently than the "main operation failed" exception?
Sep 08 2018