This seems problematic to me, because it breaks the expectation that the most important part of the stack trace can be found at the bottom of the it. In this case, if someone subsequently wraps the original exception in their own Exception the causal chain then gets jumbled. I actually wish printStackTrace processed the exceptions in the main trace in reverse compared to how it is now. If, rather than finding this at the end of a stack trace:
Caused by: org.osgi.framework.BundleException: Error starting module. at org.eclipse.osgi.container.Module.doStart(Module.java:580) at org.eclipse.osgi.container.Module.start(Module.java:439) at org.eclipse.osgi.framework.util.SecureAction.start(SecureAction.java:454) at org.eclipse.osgi.internal.hooks.EclipseLazyStarter.postFindLocalClass(EclipseLazyStarter.java:107) ... 57 more Caused by: java.lang.NoClassDefFoundError: org/eclipse/jdt/debug/core/IJavaHotCodeReplaceListener at java.lang.Class.getDeclaredConstructors0(Native Method) at java.lang.Class.privateGetDeclaredConstructors(Unknown Source) [snip] at org.eclipse.osgi.internal.framework.EquinoxBundle$EquinoxModule.startWorker(EquinoxBundle.java:318) at org.eclipse.osgi.container.Module.doStart(Module.java:571) ... 60 more Caused by: java.lang.ClassNotFoundException: org.eclipse.jdt.debug.core.IJavaHotCodeReplaceListener cannot be found by org.eclipse.jdt.debug.ui_3.6.400.qualifier at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:432) at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:345) at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:337) at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:160) at java.lang.ClassLoader.loadClass(Unknown Source) ... 69 more You would instead see it like this at the beginning of the stack trace: java.lang.ClassNotFoundException: org.eclipse.jdt.debug.core.IJavaHotCodeReplaceListener cannot be found by org.eclipse.jdt.debug.ui_3.6.400.qualifier at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:432) at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:345) at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:337) at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:160) at java.lang.ClassLoader.loadClass(Unknown Source) Causing: java.lang.NoClassDefFoundError: org/eclipse/jdt/debug/core/IJavaHotCodeReplaceListener at java.lang.Class.getDeclaredConstructors0(Native Method) at java.lang.Class.privateGetDeclaredConstructors(Unknown Source) [snip] at org.eclipse.osgi.internal.framework.EquinoxBundle$EquinoxModule.startWorker(EquinoxBundle.java:318) at org.eclipse.osgi.container.Module.doStart(Module.java:571) Causing: org.osgi.framework.BundleException: Error starting module. at org.eclipse.osgi.container.Module.doStart(Module.java:580) at org.eclipse.osgi.container.Module.start(Module.java:439) at org.eclipse.osgi.framework.util.SecureAction.start(SecureAction.java:454) at org.eclipse.osgi.internal.hooks.EclipseLazyStarter.postFindLocalClass(EclipseLazyStarter.java:107) Causing: java.lang.ClassNotFoundException: An error occurred while automatically activating bundle org.eclipse.jdt.debug.ui (186). at org.eclipse.osgi.internal.hooks.EclipseLazyStarter.postFindLocalClass(EclipseLazyStarter.java:116) at org.eclipse.osgi.internal.loader.classpath.ClasspathManager.findLocalClass(ClasspathManager.java:531) See how even the handoff from one handler to another is now readily apparent? The right place where the next part of the trace should appear is quite obvious. -- Have a nice day, Timo Sent from Mail for Windows 10 From: Peter Levart Sent: Thursday, December 10, 2015 09:04 To: Martin Buchholz;Jason Mehrens;Doug Lea Cc: core-libs-dev Subject: Re: RFR: jsr166 jdk9 integration wave 2 Hi Martin, On 12/10/2015 12:18 AM, Martin Buchholz wrote: > I finally studied ForkJoinTask.getThrowableException - I was not aware > that we were doing reflection based exception cloning. In practice it > probably muddles through, but it just seems too hacky to use in a java > core library. We're relying on common conventions, but in principle > we are invoking random code with an unknown spec. The stack traces > created are fake in the sense that they are not created naturally, and > the thrown exception may differ from the original in significant ways, > most notably missing the message string. With heroic efforts we can > make it safer, e.g. examine the bytecode for the constructors we are > invoking, and check whether they call the super constructors in the > right way, but that will be a major engineering project by itself. I assume the aim of exception cloning in ForkJoinTask.getThrowableException is to provide the user with a stack-trace that originates in its own thread. This helps trace the origin of the exception in case it's handled way up in the call stack. In addition, the original stack trace is also preserved (original exception is added as a cause). This case does not suffer from the visibility of original exception before ForkJoinTask.getThrowableException is called, so perhaps the aim could be achieved in a slightly different manner: just return the original exception, but add a newly constructed CrossThreadPropagationException as a suppressed exception to it. Printing such augmented original exception would then reveal both stack traces. What do you think? > > For CompletableFuture.whenComplete: I also keep moving in the > direction of less magic. It is perfectly possible for a well-written > whenComplete action to catch all Throwables, add the source exception > as a suppressed exception, and rethrow. In fact, perhaps people have > already tried this and discovered we incorrectly throw the source > exception. I think we should really accept our mistake and switch to > throwing the action exception rather than the source exception. > Perhaps we should only call addSuppressed if the source exception is > not already in the list of suppressed exceptions. Less is more. I would even not bother with the additional logic of conditionally adding source exception to the list of suppressed exceptions. I doubt many codes out there do that in the whenComplete handler since such exception was ignored up until now. And if anybody does that, she will get the source exception listed twice - not a big deal. If javadoc describes the behavior, this will not happen frequently. Regards, Peter > > > On Thu, Dec 3, 2015 at 9:54 AM, Jason Mehrens <jason_mehr...@hotmail.com> > wrote: >> Hi Peter, >> >> I've done this trick before to perform Throwable cloning. You have to hunt >> for the constructors in this order: >> 1. Walk the type of the cause and super types by calling >> getConstructor(String, type of cause). (java.io.WriteAbortedException and >> javax.mail.MessagingException) >> 2. Walk the type of the cause and super types by calling getConstructor(type >> of cause, String) (I've seen this but, I can't seem to find an example) >> 3. getConstructor(String) + initCause (java.io.InvalidObjectException) >> 4. Walk the type of the cause and super types by calling getConstructor(type >> of cause). (java.awt.print.PrinterIOException) >> 5. getConstructor() + initCause. (java.nio.BufferOverflowException) >> 6. Look at the return type of methods declared in the cause type and assume >> there is a constructor matching the type or no repeating types. >> (java.nio.charset.MalformedInputException, >> java.lang.EnumConstantNotPresentException, >> java.util.regex.PatternSyntaxException) >> >> In the end all of that can still fail. Exceptions can be private subclasses >> of public classes so that gets interesting with reflection too. >> >> Jason >> >> >> =========== >> What about the 4th option (keep current behavior, but try the >> best-effort with reflection to make new exception of the same type): >> >> ... >> } catch (Throwable ex) { >> if (x == null) { >> x = ex; >> } else { >> // try to create new exception with same: >> // type, cause, suppressed exceptions and >> stack-trace... >> Throwable nx; >> Class<?> xClass = x.getClass(); >> try { >> Constructor<?> xConstr = >> xClass.getConstructor(Throwable.class); >> nx = (Throwable) xConstr.newInstance(x.getCause()); >> } catch (Exception e1) { >> try { >> nx = (Throwable) xClass.newInstance(); >> nx.initCause(x.getCause()); >> } catch (Exception e2) { >> // no luck >> nx = null; >> } >> } >> if (nx != null) { >> // inherit stack-trace of original exception >> nx.setStackTrace(x.getStackTrace()); >> // inherit suppressed exceptions >> for (Throwable sx : x.getSuppressed()) { >> nx.addSuppressed(sx); >> } >> // add 'ex' as a final suppressed exception >> nx.addSuppressed(ex); >> x = nx; >> } >> } >> } >> completeThrowable(x, r); >> >> ... >> >> >> >> Note that such code and similar code in >> ForkJoinTask.getThrowableException will probably have to be modified for >> jigsaw to include dynamic addition of read-edge to the module of >> exception class... >> >> Regards, Peter >>