1 /*
  2  * Copyright (c) 2025, 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 id=default
 26  * @modules java.base/java.lang:+open jdk.management
 27  * @library /test/lib
 28  * @requires vm.continuations & vm.opt.LockingMode != 1
 29 @run junit/othervm/timeout=480 -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
 30  */
 31 
 32 /*
 33  * @test id=Xint
 34  * @modules java.base/java.lang:+open jdk.management
 35  * @library /test/lib
 36  * @requires vm.continuations & vm.opt.LockingMode != 1
 37 @run junit/othervm/timeout=480 -Xint -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
 38  */
 39 
 40 /*
 41  * @test id=Xcomp
 42  * @modules java.base/java.lang:+open jdk.management
 43  * @library /test/lib
 44  * @requires vm.continuations & vm.opt.LockingMode != 1
 45 @run junit/othervm/timeout=480 -Xcomp -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
 46  */
 47 
 48 /*
 49  * @test id=Xcomp-TieredStopAtLevel1
 50  * @modules java.base/java.lang:+open jdk.management
 51  * @library /test/lib
 52  * @requires vm.continuations & vm.opt.LockingMode != 1
 53 @run junit/othervm/timeout=480 -Xcomp -XX:TieredStopAtLevel=1 -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
 54  */
 55 
 56 /*
 57  * @test id=Xcomp-noTieredCompilation
 58  * @modules java.base/java.lang:+open jdk.management
 59  * @library /test/lib
 60  * @requires vm.continuations & vm.opt.LockingMode != 1
 61 @run junit/othervm/timeout=480 -Xcomp -XX:-TieredCompilation -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
 62  */
 63 
 64 /*
 65  * @test id=gc
 66  * @modules java.base/java.lang:+open jdk.management
 67  * @library /test/lib
 68  * @requires vm.debug == true & vm.continuations & vm.opt.LockingMode != 1
 69 @run junit/othervm/timeout=480 -XX:+UnlockDiagnosticVMOptions -XX:+FullGCALot -XX:FullGCALotInterval=1000 -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
 70  */
 71 
 72 import java.util.ArrayList;
 73 import java.util.List;
 74 import java.util.Set;
 75 import java.util.concurrent.atomic.AtomicInteger;
 76 import java.util.concurrent.atomic.AtomicBoolean;
 77 import java.util.concurrent.CountDownLatch;
 78 import java.util.concurrent.locks.LockSupport;
 79 
 80 import java.util.stream.Stream;
 81 import java.util.stream.Collectors;
 82 
 83 import org.junit.jupiter.api.Test;
 84 import static org.junit.jupiter.api.Assertions.*;
 85 import org.junit.jupiter.params.ParameterizedTest;
 86 import org.junit.jupiter.params.provider.MethodSource;
 87 import org.junit.jupiter.params.provider.Arguments;
 88 
 89 class KlassInit {
 90     static final int MAX_VTHREAD_COUNT = 8 * Runtime.getRuntime().availableProcessors();
 91     static CountDownLatch finishInvokeStatic1 = new CountDownLatch(1);
 92     static CountDownLatch finishInvokeStatic2 = new CountDownLatch(1);
 93     static CountDownLatch finishInvokeStatic3 = new CountDownLatch(1);
 94     static CountDownLatch finishNew = new CountDownLatch(1);
 95     static CountDownLatch finishGetStatic = new CountDownLatch(1);
 96     static CountDownLatch finishPutStatic = new CountDownLatch(1);
 97     static CountDownLatch finishFailedInit = new CountDownLatch(1);
 98 
 99     /**
100      * Test that threads blocked waiting for klass to be initialized
101      * on invokestatic bytecode release the carrier.
102      */
103     @Test
104     void testReleaseAtKlassInitInvokeStatic1() throws Exception {
105         class TestClass {
106             static {
107                 try {
108                     finishInvokeStatic1.await();
109                 } catch(InterruptedException e) {}
110             }
111             static void m() {
112             }
113         }
114 
115         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
116         CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
117         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
118             final int id = i;
119             started[i] = new CountDownLatch(1);
120             vthreads[i] = Thread.ofVirtual().start(() -> {
121                 started[id].countDown();
122                 TestClass.m();
123             });
124         }
125         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
126             started[i].await();
127             await(vthreads[i], Thread.State.WAITING);
128         }
129 
130         finishInvokeStatic1.countDown();
131         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
132             vthreads[i].join();
133         }
134     }
135 
136     /**
137      * Test with static method that takes arguments.
138      */
139     @Test
140     void testReleaseAtKlassInitInvokeStatic2() throws Exception {
141         class TestClass {
142             static {
143                 try {
144                     finishInvokeStatic2.await();
145                 } catch(InterruptedException e) {}
146             }
147             static void m(ArrayList<String> list, int id) {
148                 String str = list.get(0);
149                 if (str != null && str.equals("VThread#" + id)) {
150                     list.add("Success");
151                 }
152             }
153         }
154 
155         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
156         CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
157         ArrayList<String>[] lists = new ArrayList[MAX_VTHREAD_COUNT];
158         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
159             final int id = i;
160             started[i] = new CountDownLatch(1);
161             lists[i] = new ArrayList<>(List.of("VThread#" + i));
162             vthreads[i] = Thread.ofVirtual().start(() -> {
163                 started[id].countDown();
164                 TestClass.m(lists[id], id);
165             });
166         }
167         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
168             started[i].await();
169             await(vthreads[i], Thread.State.WAITING);
170         }
171 
172         System.gc();
173         finishInvokeStatic2.countDown();
174         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
175             vthreads[i].join();
176             assertEquals(lists[i].get(1), "Success");
177         }
178     }
179 
180     /**
181      * Test invokestatic as first bytecode in method.
182      */
183     @Test
184     void testReleaseAtKlassInitInvokeStatic3() throws Exception {
185         class TestClass {
186             static {
187                 try {
188                     finishInvokeStatic3.await();
189                 } catch(InterruptedException e) {}
190             }
191             static void m() {
192             }
193         }
194         class Driver {
195             static void foo() {
196                 TestClass.m();
197             }
198         }
199 
200         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
201         CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
202         started[0] = new CountDownLatch(1);
203         vthreads[0] = Thread.ofVirtual().start(() -> {
204             started[0].countDown();
205             TestClass.m();
206         });
207         started[0].await();
208         await(vthreads[0], Thread.State.WAITING);
209 
210         for (int i = 1; i < MAX_VTHREAD_COUNT; i++) {
211             final int id = i;
212             started[i] = new CountDownLatch(1);
213             vthreads[i] = Thread.ofVirtual().start(() -> {
214                 started[id].countDown();
215                 Driver.foo();
216             });
217         }
218         for (int i = 1; i < MAX_VTHREAD_COUNT; i++) {
219             started[i].await();
220             await(vthreads[i], Thread.State.WAITING);
221         }
222 
223         finishInvokeStatic3.countDown();
224         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
225             vthreads[i].join();
226         }
227     }
228 
229     /**
230      * Test that threads blocked waiting for klass to be initialized
231      * on new bytecode release the carrier.
232      */
233     @Test
234     void testReleaseAtKlassInitNew() throws Exception {
235         class TestClass {
236             static {
237                 try {
238                     finishNew.await();
239                 } catch(InterruptedException e) {}
240             }
241             void m() {
242             }
243         }
244 
245         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
246         CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
247         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
248             final int id = i;
249             started[i] = new CountDownLatch(1);
250             vthreads[i] = Thread.ofVirtual().start(() -> {
251                 started[id].countDown();
252                 TestClass x = new TestClass();
253                 x.m();
254             });
255         }
256         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
257             started[i].await();
258             await(vthreads[i], Thread.State.WAITING);
259         }
260 
261         finishNew.countDown();
262         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
263             vthreads[i].join();
264         }
265     }
266 
267     /**
268      * Test that threads blocked waiting for klass to be initialized
269      * on getstatic bytecode release the carrier.
270      */
271     @Test
272     void testReleaseAtKlassInitGetStatic() throws Exception {
273         class TestClass {
274             static {
275                 try {
276                     finishGetStatic.await();
277                 } catch(InterruptedException e) {}
278             }
279             public static int NUMBER = 150;
280         }
281 
282         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
283         CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
284         AtomicInteger[] result = new AtomicInteger[MAX_VTHREAD_COUNT];
285         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
286             final int id = i;
287             started[i] = new CountDownLatch(1);
288             result[i] = new AtomicInteger();
289             vthreads[i] = Thread.ofVirtual().start(() -> {
290                 started[id].countDown();
291                 result[id].set(TestClass.NUMBER);
292             });
293         }
294         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
295             started[i].await();
296             await(vthreads[i], Thread.State.WAITING);
297         }
298 
299         finishGetStatic.countDown();
300         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
301             vthreads[i].join();
302             assertEquals(result[i].get(), TestClass.NUMBER);
303         }
304     }
305 
306     /**
307      * Test that threads blocked waiting for klass to be initialized
308      * on putstatic release the carrier.
309      */
310     @Test
311     void testReleaseAtKlassInitPutStatic() throws Exception {
312         class TestClass {
313             static {
314                 try {
315                     finishPutStatic.await();
316                 } catch(InterruptedException e) {}
317             }
318             public static int NUMBER;
319         }
320 
321         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
322         CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
323         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
324             final int id = i;
325             started[i] = new CountDownLatch(1);
326             vthreads[i] = Thread.ofVirtual().start(() -> {
327                 started[id].countDown();
328                 TestClass.NUMBER = id;
329             });
330         }
331         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
332             started[i].await();
333             await(vthreads[i], Thread.State.WAITING);
334         }
335 
336         finishPutStatic.countDown();
337         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
338             vthreads[i].join();
339         }
340     }
341 
342     /**
343      * Test that interruptions during preemption on klass init
344      * are preserved.
345      */
346     @ParameterizedTest
347     @MethodSource("interruptTestCases")
348     void testReleaseAtKlassInitPreserverInterrupt(int timeout, Runnable m, CountDownLatch finish) throws Exception {
349         // Start vthread1 and wait until it blocks in TestClassX initializer
350         var vthread1_started = new CountDownLatch(1);
351         var vthread1 = Thread.ofVirtual().start(() -> {
352                 vthread1_started.countDown();
353                 m.run();
354             });
355         vthread1_started.await();
356         await(vthread1, Thread.State.WAITING);
357 
358         // Start vthread2 and wait until it gets preempted on TestClassX initialization
359         var lock = new Object();
360         var interruptedException = new AtomicBoolean();
361         var vthread2_started = new CountDownLatch(1);
362         var vthread2 = Thread.ofVirtual().start(() -> {
363                 vthread2_started.countDown();
364                 m.run();
365                 synchronized (lock) {
366                     try {
367                         if (timeout > 0) {
368                             lock.wait(timeout);
369                         } else {
370                             lock.wait();
371                         }
372                     } catch (InterruptedException e) {
373                         // check stack trace has the expected frames
374                         Set<String> expected = Set.of("wait0", "wait", "run");
375                         Set<String> methods = Stream.of(e.getStackTrace())
376                                 .map(StackTraceElement::getMethodName)
377                                 .collect(Collectors.toSet());
378                         assertTrue(methods.containsAll(expected));
379                         interruptedException.set(true);
380                     }
381                 }
382             });
383         vthread2_started.await();
384         await(vthread2, Thread.State.WAITING);
385 
386         // Interrupt vthread2 and let initialization of TestClassX finish
387         vthread2.interrupt();
388         finish.countDown();
389         vthread1.join();
390         vthread2.join();
391         assertTrue(interruptedException.get());
392     }
393 
394     static CountDownLatch finishInterrupt0 = new CountDownLatch(1);
395     class TestClass0 {
396         static {
397             try {
398                 finishInterrupt0.await();
399             } catch(InterruptedException e) {}
400         }
401         static void m() {}
402     }
403 
404     static CountDownLatch finishInterrupt30000 = new CountDownLatch(1);
405     class TestClass30000 {
406         static {
407             try {
408                 finishInterrupt30000.await();
409             } catch(InterruptedException e) {}
410         }
411         static void m() {}
412     }
413 
414     static CountDownLatch finishInterruptMax = new CountDownLatch(1);
415     class TestClassMax {
416         static {
417             try {
418                 finishInterruptMax.await();
419             } catch(InterruptedException e) {}
420         }
421         static void m() {}
422     }
423 
424     static Stream<Arguments> interruptTestCases() {
425         return Stream.of(
426             Arguments.of(0, (Runnable) TestClass0::m, finishInterrupt0),
427             Arguments.of(30000, (Runnable) TestClass30000::m, finishInterrupt30000),
428             Arguments.of(Integer.MAX_VALUE, (Runnable) TestClassMax::m, finishInterruptMax)
429         );
430     }
431 
432     /**
433      * Test case of threads blocked waiting for klass to be initialized
434      * when the klass initialization fails.
435      */
436     @Test
437     void testReleaseAtKlassInitFailedInit() throws Exception {
438         class TestClass {
439             static int[] a = {1, 2, 3};
440             static {
441                 try {
442                     finishFailedInit.await();
443                     a[3] = 4;
444                 } catch(InterruptedException e) {}
445             }
446             static void m() {
447             }
448         }
449 
450         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
451         CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
452         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
453             final int id = i;
454             started[i] = new CountDownLatch(1);
455             vthreads[i] = Thread.ofVirtual().start(() -> {
456                 started[id].countDown();
457                 TestClass.m();
458             });
459         }
460         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
461             started[i].await();
462             await(vthreads[i], Thread.State.WAITING);
463         }
464 
465         finishFailedInit.countDown();
466         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
467             vthreads[i].join();
468         }
469     }
470 
471     /**
472      * Waits for the given thread to reach a given state.
473      */
474     private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
475         Thread.State state = thread.getState();
476         while (state != expectedState) {
477             assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
478             Thread.sleep(10);
479             state = thread.getState();
480         }
481     }
482 }