Hi David,
On 07/09/2018 03:37 AM, David Holmes wrote:
Hi Peter,
On 7/07/2018 2:10 AM, Peter Levart wrote:
Hi,
On 07/05/2018 01:01 AM, David Holmes wrote:
I dispute "they will understand this might have happened in another
thread".
What if the stack trace was like the following...
Yes your suggestion makes it much clearer.
But ... my whole objection here is doing all this extraneous execution
of Java code in response to the initial exception. The more Java code
we execute the more likely we will hit secondary exceptions and the
greater the possibility of unintended interactions that might lead
back to the class that can't be initialized. I just don't think this
level of effort is warranted. I
I agree that more classes are involved, but they are all JDK classes and
their number is constant. Meaning, if they are OK and don't fail when
initializing, there's no danger of unintended interactions that would be
caused by initialization errors in other classes. And even if those
additional needed classes had problems in their initialization, I think
that the consequences would be under control.
Let's see what additional classes are needed when the presented patch is
used as opposed to classes needed in current logic:
In step 7, when super class/interface initialization fails and in steps
10/11 when the class initialization fails, we record the error thrown
(record_init_exception). In addition to previously needed classes we
also need:
- ClassLoader,
- ClassLoader$InitExceptionSubst (with dependencies: RuntimeException,
Exception),
- ClassLoaderValue (with dependencies: AbstractClassLoaderValue,
AbstractClassLoaderValue$Sub, AbstractClassLoaderValue$Memoizer,
ConcurrentHashMap + deps)
When we throw NoClassDefFoundError, we don't need any other additional
classes that wouldn't already be needed originally.
So I can see that when above additional classes had problems
initializing themselves, there would be errors thrown from their usage
when recording initial initialization exception of some unrelated class,
but such errors would be ignored (step 7):
979 // Record the exception thrown from super class/interface
initialization so that
980 // it can be chained into potential later
NoClassDefFoundErrors.
981 class_loader_data()->record_init_exception(java_mirror_handle(),
e, THREAD);
982 // Locks object, set state, and notify all waiting threads
983 set_initialization_state_and_notify(initialization_error, THREAD);
984 *CLEAR_PENDING_EXCEPTION*;
steps 10/11:
1037 // Record the exception that originally caused <clinit> to
fail so
1038 // it can be chained into potential later NoClassDefFoundErrors.
1039 class_loader_data()->record_init_exception(java_mirror_handle(), e,
THREAD);
1040 // Locks object, set state, and notify all waiting threads
1041 set_initialization_state_and_notify(initialization_error,
THREAD);
1042 *CLEAR_PENDING_EXCEPTION*;
It might be that those ignored exceptions would cause later use of those
additional classes to throw NoClassDefFoundError instead of
ExceptionInInitializerError (depending on whether it was an initial
attempt to initialize those additional classes or not), but I can't see
any other undesirable consequence. Do you?
I'll try to provoke initialization errors in those additional classes to
see what happens. Will get back when I have results of the experiment...
Regards, Peter
P.S.
Executing java code as part of VM logic plays well in Jigsaw for
example. If there is an acceptable fallback in case of java logic
failure, everything seems to be OK.
Cheers,
David
-----
Before patch:
1st attempt [ForkJoinPool.commonPool-worker-3]:
java.lang.ExceptionInInitializerError
at ClinitFailure.lambda$main$0(ClinitFailure.java:20)
at
java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1736)
at
java.base/java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1728)
at
java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at
java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at
java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at
java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at
java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Caused by: java.lang.RuntimeException: Can't get it!
at ClinitFailure$Faulty.<clinit>(ClinitFailure.java:12)
... 8 more
Caused by: java.lang.ArrayIndexOutOfBoundsException: Index 1 out of
bounds for length 0
at ClinitFailure$Faulty.<clinit>(ClinitFailure.java:10)
... 8 more
2nd attempt [ForkJoinPool.commonPool-worker-5]:
java.lang.NoClassDefFoundError: Could not initialize class
ClinitFailure$Faulty
at ClinitFailure.lambda$main$1(ClinitFailure.java:28)
at
java.base/java.util.concurrent.CompletableFuture$UniRun.tryFire(CompletableFuture.java:783)
at
java.base/java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:479)
at
java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at
java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at
java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at
java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at
java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
After patch:
1st attempt [ForkJoinPool.commonPool-worker-3]:
java.lang.ExceptionInInitializerError
at ClinitFailure.lambda$main$0(ClinitFailure.java:18)
at
java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1736)
at
java.base/java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1728)
at
java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at
java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at
java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at
java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at
java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Caused by: java.lang.RuntimeException: Can't get it!
at ClinitFailure$Faulty.<clinit>(ClinitFailure.java:10)
... 8 more
Caused by: java.lang.ArrayIndexOutOfBoundsException: Index 1 out of
bounds for length 0
at ClinitFailure$Faulty.<clinit>(ClinitFailure.java:8)
... 8 more
2nd attempt [ForkJoinPool.commonPool-worker-5]:
java.lang.NoClassDefFoundError: Could not initialize class
ClinitFailure$Faulty
at
java.base/java.lang.ClassLoader.throwReinitException(ClassLoader.java:3062)
at ClinitFailure.lambda$main$1(ClinitFailure.java:25)
at
java.base/java.util.concurrent.CompletableFuture$UniRun.tryFire(CompletableFuture.java:783)
at
java.base/java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:479)
at
java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at
java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at
java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at
java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at
java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Caused by: java.lang.ExceptionInInitializerError: 11 ms ago in thread
ForkJoinPool.commonPool-worker-3
at ClinitFailure.lambda$main$0(ClinitFailure.java:18)
at
java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1736)
at
java.base/java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1728)
... 5 more
Caused by: java.lang.RuntimeException: Can't get it!
at ClinitFailure$Faulty.<clinit>(ClinitFailure.java:10)
... 8 more
Caused by: java.lang.ArrayIndexOutOfBoundsException: Index 1 out of
bounds for length 0
at ClinitFailure$Faulty.<clinit>(ClinitFailure.java:8)
... 8 more
This is what gets printed by the sample program:
public class ClinitFailure {
static class Faulty {
static {
try {
int i = (new int[0])[1];
} catch (Exception e) {
throw new RuntimeException("Can't get it!", e);
}
}
}
public static void main(String[] args) throws Exception {
CompletableFuture.runAsync(() -> {
try {
new Faulty();
} catch (Throwable e) {
System.out.printf("\n1st attempt [%s]:\n\n",
Thread.currentThread().getName());
e.printStackTrace(System.out);
}
}).thenRunAsync(() -> {
try {
new Faulty();
} catch (Throwable e) {
System.out.printf("\n2nd attempt [%s]:\n\n",
Thread.currentThread().getName());
e.printStackTrace(System.out);
}
}).join();
}
}
When the following patch is applied:
http://cr.openjdk.java.net/~plevart/jdk-dev/8203826_NoClassDefFoundError.cause/webrev.01/
I took Volker's patch and modified it a bit:
- The logic to construct and throw NoClassDefFoundError and to record
initial <clinit> exception is in java now. It uses ClassLoaderValue
internal API to save the chains of exception(s) for faulty classes.
It is easier to do such logic in Java and less error prone.
- The chain of original <clinit> exception(s) is replaced with
substitutes that mimic .toString() and .printStackTrace() methods of
original chain, but don't reference any classes outside bootstrap
class loader
- The replacement chain of original exceptions adds a custom message
insert into the top exception as a hint to the user:
java.lang.ExceptionInInitializerError: 11 ms ago in thread
ForkJoinPool.commonPool-worker-3
So, what do you think of this one?
Regards, Peter