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