242bd67c6c
Reviewed-by: eosterlund, dholmes, mdoerr, dpochepk
479 lines
20 KiB
Java
479 lines
20 KiB
Java
/*
|
|
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/*
|
|
* @test
|
|
* @library /test/lib
|
|
*
|
|
* @requires !vm.graal.enabled
|
|
*
|
|
* @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -Xint -DTHROW=false -Xcheck:jni ClassInitBarrier
|
|
* @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -Xint -DTHROW=true -Xcheck:jni ClassInitBarrier
|
|
*
|
|
* @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=false -Xcheck:jni ClassInitBarrier
|
|
* @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=true -Xcheck:jni ClassInitBarrier
|
|
*
|
|
* @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=false -Xcheck:jni ClassInitBarrier
|
|
* @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=true -Xcheck:jni ClassInitBarrier
|
|
*
|
|
* @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=false -XX:CompileCommand=dontinline,*::static* -Xcheck:jni ClassInitBarrier
|
|
* @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=true -XX:CompileCommand=dontinline,*::static* -Xcheck:jni ClassInitBarrier
|
|
* @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=false -XX:CompileCommand=dontinline,*::static* -Xcheck:jni ClassInitBarrier
|
|
* @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=true -XX:CompileCommand=dontinline,*::static* -Xcheck:jni ClassInitBarrier
|
|
*
|
|
* @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=false -XX:CompileCommand=exclude,*::static* -Xcheck:jni ClassInitBarrier
|
|
* @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=true -XX:CompileCommand=exclude,*::static* -Xcheck:jni ClassInitBarrier
|
|
* @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=false -XX:CompileCommand=exclude,*::static* -Xcheck:jni ClassInitBarrier
|
|
* @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=true -XX:CompileCommand=exclude,*::static* -Xcheck:jni ClassInitBarrier
|
|
*/
|
|
|
|
import jdk.test.lib.Asserts;
|
|
|
|
import java.util.*;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.function.Consumer;
|
|
|
|
public class ClassInitBarrier {
|
|
static {
|
|
System.loadLibrary("ClassInitBarrier");
|
|
|
|
if (!init()) {
|
|
throw new Error("init failed");
|
|
}
|
|
}
|
|
|
|
static native boolean init();
|
|
|
|
static final boolean THROW = Boolean.getBoolean("THROW");
|
|
|
|
static class Test {
|
|
static class A {
|
|
static {
|
|
if (!init(B.class)) {
|
|
throw new Error("init failed");
|
|
}
|
|
|
|
changePhase(Phase.IN_PROGRESS);
|
|
runTests(); // interpreted mode
|
|
warmup(); // trigger compilation
|
|
runTests(); // compiled mode
|
|
|
|
ensureBlocked(); // ensure still blocked
|
|
maybeThrow(); // fail initialization if needed
|
|
|
|
changePhase(Phase.FINISHED);
|
|
}
|
|
|
|
static void staticM(Runnable action) { action.run(); }
|
|
static synchronized void staticS(Runnable action) { action.run(); }
|
|
static native void staticN(Runnable action);
|
|
|
|
static int staticF;
|
|
|
|
int f;
|
|
void m() {}
|
|
|
|
static native boolean init(Class<B> cls);
|
|
}
|
|
|
|
static class B extends A {}
|
|
|
|
static void testInvokeStatic(Runnable action) { A.staticM(action); }
|
|
static void testInvokeStaticSync(Runnable action) { A.staticS(action); }
|
|
static void testInvokeStaticNative(Runnable action) { A.staticN(action); }
|
|
|
|
static int testGetStatic(Runnable action) { int v = A.staticF; action.run(); return v; }
|
|
static void testPutStatic(Runnable action) { A.staticF = 1; action.run(); }
|
|
static A testNewInstanceA(Runnable action) { A obj = new A(); action.run(); return obj; }
|
|
static B testNewInstanceB(Runnable action) { B obj = new B(); action.run(); return obj; }
|
|
|
|
static int testGetField(A recv, Runnable action) { int v = recv.f; action.run(); return v; }
|
|
static void testPutField(A recv, Runnable action) { recv.f = 1; action.run(); }
|
|
static void testInvokeVirtual(A recv, Runnable action) { recv.m(); action.run(); }
|
|
|
|
static native void testInvokeStaticJNI(Runnable action);
|
|
static native void testInvokeStaticSyncJNI(Runnable action);
|
|
static native void testInvokeStaticNativeJNI(Runnable action);
|
|
|
|
static native int testGetStaticJNI(Runnable action);
|
|
static native void testPutStaticJNI(Runnable action);
|
|
static native A testNewInstanceAJNI(Runnable action);
|
|
static native B testNewInstanceBJNI(Runnable action);
|
|
|
|
static native int testGetFieldJNI(A recv, Runnable action);
|
|
static native void testPutFieldJNI(A recv, Runnable action);
|
|
static native void testInvokeVirtualJNI(A recv, Runnable action);
|
|
|
|
static void runTests() {
|
|
checkBlockingAction(Test::testInvokeStatic); // invokestatic
|
|
checkBlockingAction(Test::testInvokeStaticSync); // invokestatic
|
|
checkBlockingAction(Test::testInvokeStaticNative); // invokestatic
|
|
checkBlockingAction(Test::testGetStatic); // getstatic
|
|
checkBlockingAction(Test::testPutStatic); // putstatic
|
|
checkBlockingAction(Test::testNewInstanceA); // new
|
|
|
|
checkNonBlockingAction(Test::testInvokeStaticJNI); // invokestatic
|
|
checkNonBlockingAction(Test::testInvokeStaticSyncJNI); // invokestatic
|
|
checkNonBlockingAction(Test::testInvokeStaticNativeJNI); // invokestatic
|
|
checkNonBlockingAction(Test::testGetStaticJNI); // getstatic
|
|
checkNonBlockingAction(Test::testPutStaticJNI); // putstatic
|
|
checkBlockingAction(Test::testNewInstanceAJNI); // new
|
|
|
|
A recv = testNewInstanceB(NON_BLOCKING.get()); // trigger B initialization
|
|
checkNonBlockingAction(Test::testNewInstanceB); // new: NO BLOCKING: same thread: A being initialized, B fully initialized
|
|
|
|
checkNonBlockingAction(recv, Test::testGetField); // getfield
|
|
checkNonBlockingAction(recv, Test::testPutField); // putfield
|
|
checkNonBlockingAction(recv, Test::testInvokeVirtual); // invokevirtual
|
|
|
|
checkNonBlockingAction(Test::testNewInstanceBJNI); // new: NO BLOCKING: same thread: A being initialized, B fully initialized
|
|
checkNonBlockingAction(recv, Test::testGetFieldJNI); // getfield
|
|
checkNonBlockingAction(recv, Test::testPutFieldJNI); // putfield
|
|
checkNonBlockingAction(recv, Test::testInvokeVirtualJNI); // invokevirtual
|
|
}
|
|
|
|
static void warmup() {
|
|
for (int i = 0; i < 20_000; i++) {
|
|
testInvokeStatic( NON_BLOCKING_WARMUP);
|
|
testInvokeStaticNative(NON_BLOCKING_WARMUP);
|
|
testInvokeStaticSync( NON_BLOCKING_WARMUP);
|
|
testGetStatic( NON_BLOCKING_WARMUP);
|
|
testPutStatic( NON_BLOCKING_WARMUP);
|
|
testNewInstanceA( NON_BLOCKING_WARMUP);
|
|
testNewInstanceB( NON_BLOCKING_WARMUP);
|
|
|
|
testGetField(new B(), NON_BLOCKING_WARMUP);
|
|
testPutField(new B(), NON_BLOCKING_WARMUP);
|
|
testInvokeVirtual(new B(), NON_BLOCKING_WARMUP);
|
|
}
|
|
}
|
|
|
|
static void run() {
|
|
execute(ExceptionInInitializerError.class, () -> triggerInitialization(A.class));
|
|
ensureFinished();
|
|
runTests(); // after initialization is over
|
|
}
|
|
}
|
|
|
|
// ============================================================================================================== //
|
|
|
|
static void execute(Class<? extends Throwable> expectedExceptionClass, Runnable action) {
|
|
try {
|
|
action.run();
|
|
if (THROW) throw failure("no exception thrown");
|
|
} catch (Throwable e) {
|
|
if (THROW) {
|
|
if (e.getClass() == expectedExceptionClass) {
|
|
// expected
|
|
} else {
|
|
String msg = String.format("unexpected exception thrown: expected %s, caught %s",
|
|
expectedExceptionClass.getName(), e);
|
|
throw failure(msg, e);
|
|
}
|
|
} else {
|
|
throw failure("no exception expected", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static AssertionError failure(String msg) {
|
|
return new AssertionError(phase + ": " + msg);
|
|
}
|
|
|
|
private static AssertionError failure(String msg, Throwable e) {
|
|
return new AssertionError(phase + ": " + msg, e);
|
|
}
|
|
|
|
static final List<Thread> BLOCKED_THREADS = Collections.synchronizedList(new ArrayList<>());
|
|
static final Consumer<Thread> ON_BLOCK = BLOCKED_THREADS::add;
|
|
|
|
static final Map<Thread,Throwable> FAILED_THREADS = Collections.synchronizedMap(new HashMap<>());
|
|
static final Thread.UncaughtExceptionHandler ON_FAILURE = FAILED_THREADS::put;
|
|
|
|
private static void ensureBlocked() {
|
|
for (Thread thr : BLOCKED_THREADS) {
|
|
try {
|
|
thr.join(100);
|
|
if (!thr.isAlive()) {
|
|
dump(thr);
|
|
throw new AssertionError("not blocked");
|
|
}
|
|
} catch (InterruptedException e) {
|
|
throw new Error(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private static void ensureFinished() {
|
|
for (Thread thr : BLOCKED_THREADS) {
|
|
try {
|
|
thr.join(15_000);
|
|
} catch (InterruptedException e) {
|
|
throw new Error(e);
|
|
}
|
|
if (thr.isAlive()) {
|
|
dump(thr);
|
|
throw new AssertionError(thr + ": still blocked");
|
|
}
|
|
}
|
|
for (Thread thr : BLOCKED_THREADS) {
|
|
if (THROW) {
|
|
if (!FAILED_THREADS.containsKey(thr)) {
|
|
throw new AssertionError(thr + ": exception not thrown");
|
|
}
|
|
|
|
Throwable ex = FAILED_THREADS.get(thr);
|
|
if (ex.getClass() != NoClassDefFoundError.class) {
|
|
throw new AssertionError(thr + ": wrong exception thrown", ex);
|
|
}
|
|
} else {
|
|
if (FAILED_THREADS.containsKey(thr)) {
|
|
Throwable ex = FAILED_THREADS.get(thr);
|
|
throw new AssertionError(thr + ": exception thrown", ex);
|
|
}
|
|
}
|
|
}
|
|
if (THROW) {
|
|
Asserts.assertEquals(BLOCKING_COUNTER.get(), 0);
|
|
} else {
|
|
Asserts.assertEquals(BLOCKING_COUNTER.get(), BLOCKING_ACTIONS.get());
|
|
}
|
|
|
|
dumpInfo();
|
|
}
|
|
|
|
interface TestCase0 {
|
|
void run(Runnable runnable);
|
|
}
|
|
|
|
interface TestCase1<T> {
|
|
void run(T arg, Runnable runnable);
|
|
}
|
|
|
|
enum Phase { BEFORE_INIT, IN_PROGRESS, FINISHED, INIT_FAILURE }
|
|
|
|
static volatile Phase phase = Phase.BEFORE_INIT;
|
|
|
|
static void changePhase(Phase newPhase) {
|
|
dumpInfo();
|
|
|
|
Phase oldPhase = phase;
|
|
switch (oldPhase) {
|
|
case BEFORE_INIT:
|
|
Asserts.assertEquals(NON_BLOCKING_ACTIONS.get(), 0);
|
|
Asserts.assertEquals(NON_BLOCKING_COUNTER.get(), 0);
|
|
|
|
Asserts.assertEquals(BLOCKING_ACTIONS.get(), 0);
|
|
Asserts.assertEquals(BLOCKING_COUNTER.get(), 0);
|
|
break;
|
|
case IN_PROGRESS:
|
|
Asserts.assertEquals(NON_BLOCKING_COUNTER.get(), NON_BLOCKING_ACTIONS.get());
|
|
|
|
Asserts.assertEquals(BLOCKING_COUNTER.get(), 0);
|
|
break;
|
|
default: throw new Error("wrong phase transition " + oldPhase);
|
|
}
|
|
phase = newPhase;
|
|
}
|
|
|
|
static void dumpInfo() {
|
|
System.out.println("Phase: " + phase);
|
|
System.out.println("Non-blocking actions: " + NON_BLOCKING_COUNTER.get() + " / " + NON_BLOCKING_ACTIONS.get());
|
|
System.out.println("Blocking actions: " + BLOCKING_COUNTER.get() + " / " + BLOCKING_ACTIONS.get());
|
|
}
|
|
|
|
static final Runnable NON_BLOCKING_WARMUP = () -> {
|
|
if (phase != Phase.IN_PROGRESS) {
|
|
throw new AssertionError("NON_BLOCKING: wrong phase: " + phase);
|
|
}
|
|
};
|
|
|
|
static Runnable disposableAction(final Phase validPhase, final AtomicInteger invocationCounter, final AtomicInteger actionCounter) {
|
|
actionCounter.incrementAndGet();
|
|
|
|
final AtomicBoolean cnt = new AtomicBoolean(false);
|
|
return () -> {
|
|
if (cnt.getAndSet(true)) {
|
|
throw new Error("repeated invocation");
|
|
}
|
|
invocationCounter.incrementAndGet();
|
|
if (phase != validPhase) {
|
|
throw new AssertionError("NON_BLOCKING: wrong phase: " + phase);
|
|
}
|
|
};
|
|
}
|
|
|
|
@FunctionalInterface
|
|
interface Factory<V> {
|
|
V get();
|
|
}
|
|
|
|
static final AtomicInteger NON_BLOCKING_COUNTER = new AtomicInteger(0);
|
|
static final AtomicInteger NON_BLOCKING_ACTIONS = new AtomicInteger(0);
|
|
static final Factory<Runnable> NON_BLOCKING = () -> disposableAction(phase, NON_BLOCKING_COUNTER, NON_BLOCKING_ACTIONS);
|
|
|
|
static final AtomicInteger BLOCKING_COUNTER = new AtomicInteger(0);
|
|
static final AtomicInteger BLOCKING_ACTIONS = new AtomicInteger(0);
|
|
static final Factory<Runnable> BLOCKING = () -> disposableAction(Phase.FINISHED, BLOCKING_COUNTER, BLOCKING_ACTIONS);
|
|
|
|
static void checkBlockingAction(TestCase0 r) {
|
|
switch (phase) {
|
|
case IN_PROGRESS: {
|
|
// Barrier during class initalization.
|
|
r.run(NON_BLOCKING.get()); // initializing thread
|
|
checkBlocked(ON_BLOCK, ON_FAILURE, r); // different thread
|
|
break;
|
|
}
|
|
case FINISHED: {
|
|
// No barrier after class initalization is over.
|
|
r.run(NON_BLOCKING.get()); // initializing thread
|
|
checkNotBlocked(r); // different thread
|
|
break;
|
|
}
|
|
case INIT_FAILURE: {
|
|
// Exception is thrown after class initialization failed.
|
|
TestCase0 test = action -> execute(NoClassDefFoundError.class, () -> r.run(action));
|
|
|
|
test.run(NON_BLOCKING.get()); // initializing thread
|
|
checkNotBlocked(test); // different thread
|
|
break;
|
|
}
|
|
default: throw new Error("wrong phase: " + phase);
|
|
}
|
|
}
|
|
|
|
static void checkNonBlockingAction(TestCase0 r) {
|
|
r.run(NON_BLOCKING.get()); // initializing thread
|
|
checkNotBlocked(r); // different thread
|
|
}
|
|
|
|
static <T> void checkNonBlockingAction(T recv, TestCase1<T> r) {
|
|
r.run(recv, NON_BLOCKING.get()); // initializing thread
|
|
checkNotBlocked((action) -> r.run(recv, action)); // different thread
|
|
}
|
|
|
|
static void checkFailingAction(TestCase0 r) {
|
|
r.run(NON_BLOCKING.get()); // initializing thread
|
|
checkNotBlocked(r); // different thread
|
|
}
|
|
|
|
static void triggerInitialization(Class<?> cls) {
|
|
try {
|
|
Class<?> loadedClass = Class.forName(cls.getName(), true, cls.getClassLoader());
|
|
if (loadedClass != cls) {
|
|
throw new Error("wrong class");
|
|
}
|
|
} catch (ClassNotFoundException e) {
|
|
throw new Error(e);
|
|
}
|
|
}
|
|
|
|
static void checkBlocked(Consumer<Thread> onBlockHandler, Thread.UncaughtExceptionHandler onException, TestCase0 r) {
|
|
Thread thr = new Thread(() -> {
|
|
try {
|
|
r.run(BLOCKING.get());
|
|
System.out.println("Thread " + Thread.currentThread() + ": Finished successfully");
|
|
} catch(Throwable e) {
|
|
System.out.println("Thread " + Thread.currentThread() + ": Exception thrown: " + e);
|
|
if (!THROW) {
|
|
e.printStackTrace();
|
|
}
|
|
throw e;
|
|
}
|
|
} );
|
|
thr.setUncaughtExceptionHandler(onException);
|
|
|
|
thr.start();
|
|
try {
|
|
thr.join(100);
|
|
|
|
dump(thr);
|
|
if (thr.isAlive()) {
|
|
onBlockHandler.accept(thr); // blocked
|
|
} else {
|
|
throw new AssertionError("not blocked");
|
|
}
|
|
} catch (InterruptedException e) {
|
|
throw new Error(e);
|
|
}
|
|
}
|
|
|
|
static void checkNotBlocked(TestCase0 r) {
|
|
final Thread thr = new Thread(() -> r.run(NON_BLOCKING.get()));
|
|
final Throwable[] ex = new Throwable[1];
|
|
thr.setUncaughtExceptionHandler((t, e) -> {
|
|
if (thr != t) {
|
|
ex[0] = new Error("wrong thread: " + thr + " vs " + t);
|
|
} else {
|
|
ex[0] = e;
|
|
}
|
|
});
|
|
|
|
thr.start();
|
|
try {
|
|
thr.join(15_000);
|
|
if (thr.isAlive()) {
|
|
dump(thr);
|
|
throw new AssertionError("blocked");
|
|
}
|
|
} catch (InterruptedException e) {
|
|
throw new Error(e);
|
|
}
|
|
|
|
if (ex[0] != null) {
|
|
throw new AssertionError("no exception expected", ex[0]);
|
|
}
|
|
}
|
|
|
|
static void maybeThrow() {
|
|
if (THROW) {
|
|
changePhase(Phase.INIT_FAILURE);
|
|
throw new RuntimeException("failed class initialization");
|
|
}
|
|
}
|
|
|
|
private static void dump(Thread thr) {
|
|
System.out.println("Thread: " + thr);
|
|
System.out.println("Thread state: " + thr.getState());
|
|
if (thr.isAlive()) {
|
|
for (StackTraceElement frame : thr.getStackTrace()) {
|
|
System.out.println(frame);
|
|
}
|
|
} else {
|
|
if (FAILED_THREADS.containsKey(thr)) {
|
|
System.out.println("Failed with an exception: ");
|
|
FAILED_THREADS.get(thr).toString();
|
|
} else {
|
|
System.out.println("Finished successfully");
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
Test.run();
|
|
System.out.println("TEST PASSED");
|
|
}
|
|
}
|