|
1 /* |
|
2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. |
|
8 * |
|
9 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
12 * version 2 for more details (a copy is included in the LICENSE file that |
|
13 * accompanied this code). |
|
14 * |
|
15 * You should have received a copy of the GNU General Public License version |
|
16 * 2 along with this work; if not, write to the Free Software Foundation, |
|
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
18 * |
|
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
20 * or visit www.oracle.com if you need additional information or have any |
|
21 * questions. |
|
22 */ |
|
23 |
|
24 /* |
|
25 * @test |
|
26 * @library /test/lib |
|
27 * |
|
28 * @requires !vm.graal.enabled |
|
29 * |
|
30 * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -Xint -DTHROW=false ClassInitBarrier |
|
31 * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -Xint -DTHROW=true ClassInitBarrier |
|
32 * |
|
33 * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=false ClassInitBarrier |
|
34 * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=true ClassInitBarrier |
|
35 * |
|
36 * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=false ClassInitBarrier |
|
37 * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=true ClassInitBarrier |
|
38 * |
|
39 * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=false -XX:CompileCommand=dontinline,*::static* ClassInitBarrier |
|
40 * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=true -XX:CompileCommand=dontinline,*::static* ClassInitBarrier |
|
41 * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=false -XX:CompileCommand=dontinline,*::static* ClassInitBarrier |
|
42 * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=true -XX:CompileCommand=dontinline,*::static* ClassInitBarrier |
|
43 * |
|
44 * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=false -XX:CompileCommand=exclude,*::static* ClassInitBarrier |
|
45 * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=true -XX:CompileCommand=exclude,*::static* ClassInitBarrier |
|
46 * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=false -XX:CompileCommand=exclude,*::static* ClassInitBarrier |
|
47 * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=true -XX:CompileCommand=exclude,*::static* ClassInitBarrier |
|
48 */ |
|
49 |
|
50 import jdk.test.lib.Asserts; |
|
51 |
|
52 import java.util.*; |
|
53 import java.util.concurrent.atomic.AtomicBoolean; |
|
54 import java.util.concurrent.atomic.AtomicInteger; |
|
55 import java.util.function.Consumer; |
|
56 |
|
57 public class ClassInitBarrier { |
|
58 static { |
|
59 System.loadLibrary("ClassInitBarrier"); |
|
60 |
|
61 if (!init()) { |
|
62 throw new Error("init failed"); |
|
63 } |
|
64 } |
|
65 |
|
66 static native boolean init(); |
|
67 |
|
68 static final boolean THROW = Boolean.getBoolean("THROW"); |
|
69 |
|
70 static class Test { |
|
71 static class A { |
|
72 static { |
|
73 changePhase(Phase.IN_PROGRESS); |
|
74 runTests(); // interpreted mode |
|
75 warmup(); // trigger compilation |
|
76 runTests(); // compiled mode |
|
77 |
|
78 ensureBlocked(); // ensure still blocked |
|
79 maybeThrow(); // fail initialization if needed |
|
80 |
|
81 changePhase(Phase.FINISHED); |
|
82 } |
|
83 |
|
84 static void staticM(Runnable action) { action.run(); } |
|
85 static synchronized void staticS(Runnable action) { action.run(); } |
|
86 static native void staticN(Runnable action); |
|
87 |
|
88 static int staticF; |
|
89 |
|
90 int f; |
|
91 void m() {} |
|
92 } |
|
93 |
|
94 static class B extends A {} |
|
95 |
|
96 static void testInvokeStatic(Runnable action) { A.staticM(action); } |
|
97 static void testInvokeStaticSync(Runnable action) { A.staticS(action); } |
|
98 static void testInvokeStaticNative(Runnable action) { A.staticN(action); } |
|
99 |
|
100 static int testGetStatic(Runnable action) { int v = A.staticF; action.run(); return v; } |
|
101 static void testPutStatic(Runnable action) { A.staticF = 1; action.run(); } |
|
102 static A testNewInstanceA(Runnable action) { A obj = new A(); action.run(); return obj; } |
|
103 static B testNewInstanceB(Runnable action) { B obj = new B(); action.run(); return obj; } |
|
104 |
|
105 static int testGetField(A recv, Runnable action) { int v = recv.f; action.run(); return v; } |
|
106 static void testPutField(A recv, Runnable action) { recv.f = 1; action.run(); } |
|
107 static void testInvokeVirtual(A recv, Runnable action) { recv.m(); action.run(); } |
|
108 |
|
109 static void runTests() { |
|
110 checkBlockingAction(Test::testInvokeStatic); // invokestatic |
|
111 checkBlockingAction(Test::testInvokeStaticNative); // invokestatic |
|
112 checkBlockingAction(Test::testInvokeStaticSync); // invokestatic |
|
113 checkBlockingAction(Test::testGetStatic); // getstatic |
|
114 checkBlockingAction(Test::testPutStatic); // putstatic |
|
115 checkBlockingAction(Test::testNewInstanceA); // new |
|
116 |
|
117 A recv = testNewInstanceB(NON_BLOCKING.get()); // trigger B initialization |
|
118 checkNonBlockingAction(Test::testNewInstanceB); // new: NO BLOCKING: same thread: A being initialized, B fully initialized |
|
119 |
|
120 checkNonBlockingAction(recv, Test::testGetField); // getfield |
|
121 checkNonBlockingAction(recv, Test::testPutField); // putfield |
|
122 checkNonBlockingAction(recv, Test::testInvokeVirtual); // invokevirtual |
|
123 } |
|
124 |
|
125 static void warmup() { |
|
126 for (int i = 0; i < 20_000; i++) { |
|
127 testInvokeStatic( NON_BLOCKING_WARMUP); |
|
128 testInvokeStaticNative(NON_BLOCKING_WARMUP); |
|
129 testInvokeStaticSync( NON_BLOCKING_WARMUP); |
|
130 testGetStatic( NON_BLOCKING_WARMUP); |
|
131 testPutStatic( NON_BLOCKING_WARMUP); |
|
132 testNewInstanceA( NON_BLOCKING_WARMUP); |
|
133 testNewInstanceB( NON_BLOCKING_WARMUP); |
|
134 |
|
135 testGetField(new B(), NON_BLOCKING_WARMUP); |
|
136 testPutField(new B(), NON_BLOCKING_WARMUP); |
|
137 testInvokeVirtual(new B(), NON_BLOCKING_WARMUP); |
|
138 } |
|
139 } |
|
140 |
|
141 static void run() { |
|
142 execute(ExceptionInInitializerError.class, () -> triggerInitialization(A.class)); |
|
143 |
|
144 ensureFinished(); |
|
145 } |
|
146 } |
|
147 |
|
148 // ============================================================================================================== // |
|
149 |
|
150 static void execute(Class<? extends Throwable> expectedExceptionClass, Runnable action) { |
|
151 try { |
|
152 action.run(); |
|
153 if (THROW) throw new AssertionError("no exception thrown"); |
|
154 } catch (Throwable e) { |
|
155 if (THROW) { |
|
156 if (e.getClass() == expectedExceptionClass) { |
|
157 // expected |
|
158 } else { |
|
159 String msg = String.format("unexpected exception thrown: expected %s, caught %s", |
|
160 expectedExceptionClass.getName(), e.getClass().getName()); |
|
161 throw new AssertionError(msg, e); |
|
162 } |
|
163 } else { |
|
164 throw new AssertionError("no exception expected", e); |
|
165 } |
|
166 } |
|
167 } |
|
168 |
|
169 static final List<Thread> BLOCKED_THREADS = Collections.synchronizedList(new ArrayList<>()); |
|
170 static final Consumer<Thread> ON_BLOCK = BLOCKED_THREADS::add; |
|
171 |
|
172 static final Map<Thread,Throwable> FAILED_THREADS = Collections.synchronizedMap(new HashMap<>()); |
|
173 static final Thread.UncaughtExceptionHandler ON_FAILURE = FAILED_THREADS::put; |
|
174 |
|
175 private static void ensureBlocked() { |
|
176 for (Thread thr : BLOCKED_THREADS) { |
|
177 try { |
|
178 thr.join(100); |
|
179 if (!thr.isAlive()) { |
|
180 dump(thr); |
|
181 throw new AssertionError("not blocked"); |
|
182 } |
|
183 } catch (InterruptedException e) { |
|
184 throw new Error(e); |
|
185 } |
|
186 } |
|
187 } |
|
188 |
|
189 |
|
190 private static void ensureFinished() { |
|
191 for (Thread thr : BLOCKED_THREADS) { |
|
192 try { |
|
193 thr.join(15_000); |
|
194 } catch (InterruptedException e) { |
|
195 throw new Error(e); |
|
196 } |
|
197 if (thr.isAlive()) { |
|
198 dump(thr); |
|
199 throw new AssertionError(thr + ": still blocked"); |
|
200 } |
|
201 } |
|
202 for (Thread thr : BLOCKED_THREADS) { |
|
203 if (THROW) { |
|
204 if (!FAILED_THREADS.containsKey(thr)) { |
|
205 throw new AssertionError(thr + ": exception not thrown"); |
|
206 } |
|
207 |
|
208 Throwable ex = FAILED_THREADS.get(thr); |
|
209 if (ex.getClass() != NoClassDefFoundError.class) { |
|
210 throw new AssertionError(thr + ": wrong exception thrown", ex); |
|
211 } |
|
212 } else { |
|
213 if (FAILED_THREADS.containsKey(thr)) { |
|
214 Throwable ex = FAILED_THREADS.get(thr); |
|
215 throw new AssertionError(thr + ": exception thrown", ex); |
|
216 } |
|
217 } |
|
218 } |
|
219 if (THROW) { |
|
220 Asserts.assertEquals(BLOCKING_COUNTER.get(), 0); |
|
221 } else { |
|
222 Asserts.assertEquals(BLOCKING_COUNTER.get(), BLOCKING_ACTIONS.get()); |
|
223 } |
|
224 |
|
225 dumpInfo(); |
|
226 } |
|
227 |
|
228 interface TestCase0 { |
|
229 void run(Runnable runnable); |
|
230 } |
|
231 |
|
232 interface TestCase1<T> { |
|
233 void run(T arg, Runnable runnable); |
|
234 } |
|
235 |
|
236 enum Phase { BEFORE_INIT, IN_PROGRESS, FINISHED, INIT_FAILURE } |
|
237 |
|
238 static volatile Phase phase = Phase.BEFORE_INIT; |
|
239 |
|
240 static void changePhase(Phase newPhase) { |
|
241 dumpInfo(); |
|
242 |
|
243 Phase oldPhase = phase; |
|
244 switch (oldPhase) { |
|
245 case BEFORE_INIT: |
|
246 Asserts.assertEquals(NON_BLOCKING_ACTIONS.get(), 0); |
|
247 Asserts.assertEquals(NON_BLOCKING_COUNTER.get(), 0); |
|
248 |
|
249 Asserts.assertEquals(BLOCKING_ACTIONS.get(), 0); |
|
250 Asserts.assertEquals(BLOCKING_COUNTER.get(), 0); |
|
251 break; |
|
252 case IN_PROGRESS: |
|
253 Asserts.assertEquals(NON_BLOCKING_COUNTER.get(), NON_BLOCKING_ACTIONS.get()); |
|
254 |
|
255 Asserts.assertEquals(BLOCKING_COUNTER.get(), 0); |
|
256 break; |
|
257 default: throw new Error("wrong phase transition " + oldPhase); |
|
258 } |
|
259 phase = newPhase; |
|
260 } |
|
261 |
|
262 static void dumpInfo() { |
|
263 System.out.println("Phase: " + phase); |
|
264 System.out.println("Non-blocking actions: " + NON_BLOCKING_COUNTER.get() + " / " + NON_BLOCKING_ACTIONS.get()); |
|
265 System.out.println("Blocking actions: " + BLOCKING_COUNTER.get() + " / " + BLOCKING_ACTIONS.get()); |
|
266 } |
|
267 |
|
268 static final Runnable NON_BLOCKING_WARMUP = () -> { |
|
269 if (phase != Phase.IN_PROGRESS) { |
|
270 throw new AssertionError("NON_BLOCKING: wrong phase: " + phase); |
|
271 } |
|
272 }; |
|
273 |
|
274 static Runnable disposableAction(final Phase validPhase, final AtomicInteger invocationCounter, final AtomicInteger actionCounter) { |
|
275 actionCounter.incrementAndGet(); |
|
276 |
|
277 final AtomicBoolean cnt = new AtomicBoolean(false); |
|
278 return () -> { |
|
279 if (cnt.getAndSet(true)) { |
|
280 throw new Error("repeated invocation"); |
|
281 } |
|
282 invocationCounter.incrementAndGet(); |
|
283 if (phase != validPhase) { |
|
284 throw new AssertionError("NON_BLOCKING: wrong phase: " + phase); |
|
285 } |
|
286 }; |
|
287 } |
|
288 |
|
289 @FunctionalInterface |
|
290 interface Factory<V> { |
|
291 V get(); |
|
292 } |
|
293 |
|
294 static final AtomicInteger NON_BLOCKING_COUNTER = new AtomicInteger(0); |
|
295 static final AtomicInteger NON_BLOCKING_ACTIONS = new AtomicInteger(0); |
|
296 static final Factory<Runnable> NON_BLOCKING = () -> disposableAction(Phase.IN_PROGRESS, NON_BLOCKING_COUNTER, NON_BLOCKING_ACTIONS); |
|
297 |
|
298 static final AtomicInteger BLOCKING_COUNTER = new AtomicInteger(0); |
|
299 static final AtomicInteger BLOCKING_ACTIONS = new AtomicInteger(0); |
|
300 static final Factory<Runnable> BLOCKING = () -> disposableAction(Phase.FINISHED, BLOCKING_COUNTER, BLOCKING_ACTIONS); |
|
301 |
|
302 static void checkBlockingAction(TestCase0 r) { |
|
303 r.run(NON_BLOCKING.get()); // same thread |
|
304 checkBlocked(ON_BLOCK, ON_FAILURE, r); // different thread |
|
305 } |
|
306 |
|
307 static void checkNonBlockingAction(TestCase0 r) { |
|
308 r.run(NON_BLOCKING.get()); |
|
309 checkNotBlocked(r); // different thread |
|
310 } |
|
311 |
|
312 static <T> void checkNonBlockingAction(T recv, TestCase1<T> r) { |
|
313 r.run(recv, NON_BLOCKING.get()); // same thread |
|
314 checkNotBlocked((action) -> r.run(recv, action)); // different thread |
|
315 } |
|
316 |
|
317 static void triggerInitialization(Class<?> cls) { |
|
318 try { |
|
319 Class<?> loadedClass = Class.forName(cls.getName(), true, cls.getClassLoader()); |
|
320 if (loadedClass != cls) { |
|
321 throw new Error("wrong class"); |
|
322 } |
|
323 } catch (ClassNotFoundException e) { |
|
324 throw new Error(e); |
|
325 } |
|
326 } |
|
327 |
|
328 static void checkBlocked(Consumer<Thread> onBlockHandler, Thread.UncaughtExceptionHandler onException, TestCase0 r) { |
|
329 Thread thr = new Thread(() -> { |
|
330 try { |
|
331 r.run(BLOCKING.get()); |
|
332 System.out.println("Thread " + Thread.currentThread() + ": Finished successfully"); |
|
333 } catch(Throwable e) { |
|
334 System.out.println("Thread " + Thread.currentThread() + ": Exception thrown: " + e); |
|
335 if (!THROW) { |
|
336 e.printStackTrace(); |
|
337 } |
|
338 throw e; |
|
339 } |
|
340 } ); |
|
341 thr.setUncaughtExceptionHandler(onException); |
|
342 |
|
343 thr.start(); |
|
344 try { |
|
345 thr.join(100); |
|
346 |
|
347 dump(thr); |
|
348 if (thr.isAlive()) { |
|
349 onBlockHandler.accept(thr); // blocked |
|
350 } else { |
|
351 throw new AssertionError("not blocked"); |
|
352 } |
|
353 } catch (InterruptedException e) { |
|
354 throw new Error(e); |
|
355 } |
|
356 } |
|
357 |
|
358 static void checkNotBlocked(TestCase0 r) { |
|
359 Thread thr = new Thread(() -> r.run(NON_BLOCKING.get())); |
|
360 |
|
361 thr.start(); |
|
362 try { |
|
363 thr.join(15_000); |
|
364 if (thr.isAlive()) { |
|
365 dump(thr); |
|
366 throw new AssertionError("blocked"); |
|
367 } |
|
368 } catch (InterruptedException e) { |
|
369 throw new Error(e); |
|
370 } |
|
371 } |
|
372 |
|
373 static void maybeThrow() { |
|
374 if (THROW) { |
|
375 changePhase(Phase.INIT_FAILURE); |
|
376 throw new RuntimeException("failed class initialization"); |
|
377 } |
|
378 } |
|
379 |
|
380 private static void dump(Thread thr) { |
|
381 System.out.println("Thread: " + thr); |
|
382 System.out.println("Thread state: " + thr.getState()); |
|
383 if (thr.isAlive()) { |
|
384 for (StackTraceElement frame : thr.getStackTrace()) { |
|
385 System.out.println(frame); |
|
386 } |
|
387 } else { |
|
388 if (FAILED_THREADS.containsKey(thr)) { |
|
389 System.out.println("Failed with an exception: "); |
|
390 FAILED_THREADS.get(thr).toString(); |
|
391 } else { |
|
392 System.out.println("Finished successfully"); |
|
393 } |
|
394 } |
|
395 } |
|
396 |
|
397 public static void main(String[] args) throws Exception { |
|
398 Test.run(); |
|
399 System.out.println("TEST PASSED"); |
|
400 } |
|
401 } |