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 -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 -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 -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 -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 -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 -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 
 98     /**
 99      * Test that threads blocked waiting for klass to be initialized
100      * on invokestatic bytecode release the carrier.
101      */
102     @Test
103     void testReleaseAtKlassInitInvokeStatic1() throws Exception {
104         class TestClass {
105             static {
106                 try {
107                     finishInvokeStatic1.await();
108                 } catch(InterruptedException e) {}
109             }
110             static void m() {
111             }
112         }
113 
114         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
115         CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
116         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
117             final int id = i;
118             started[i] = new CountDownLatch(1);
119             vthreads[i] = Thread.ofVirtual().start(() -> {
120                 started[id].countDown();
121                 TestClass.m();
122             });
123         }
124         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
125             started[i].await();
126             await(vthreads[i], Thread.State.WAITING);
127         }
128 
129         finishInvokeStatic1.countDown();
130         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
131             vthreads[i].join();
132         }
133     }
134 
135     /**
136      * Test with static method that takes arguments.
137      */
138     @Test
139     void testReleaseAtKlassInitInvokeStatic2() throws Exception {
140         class TestClass {
141             static {
142                 try {
143                     finishInvokeStatic2.await();
144                 } catch(InterruptedException e) {}
145             }
146             static void m(ArrayList<String> list, int id) {
147                 String str = list.get(0);
148                 if (str != null && str.equals("VThread#" + id)) {
149                     list.add("Success");
150                 }
151             }
152         }
153 
154         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
155         CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
156         ArrayList<String>[] lists = new ArrayList[MAX_VTHREAD_COUNT];
157         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
158             final int id = i;
159             started[i] = new CountDownLatch(1);
160             lists[i] = new ArrayList<>(List.of("VThread#" + i));
161             vthreads[i] = Thread.ofVirtual().start(() -> {
162                 started[id].countDown();
163                 TestClass.m(lists[id], id);
164             });
165         }
166         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
167             started[i].await();
168             await(vthreads[i], Thread.State.WAITING);
169         }
170 
171         System.gc();
172         finishInvokeStatic2.countDown();
173         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
174             vthreads[i].join();
175             assertEquals(lists[i].get(1), "Success");
176         }
177     }
178 
179     /**
180      * Test invokestatic as first bytecode in method.
181      */
182     @Test
183     void testReleaseAtKlassInitInvokeStatic3() throws Exception {
184         class TestClass {
185             static {
186                 try {
187                     finishInvokeStatic3.await();
188                 } catch(InterruptedException e) {}
189             }
190             static void m() {
191             }
192         }
193         class Driver {
194             static void foo() {
195                 TestClass.m();
196             }
197         }
198 
199         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
200         CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
201         started[0] = new CountDownLatch(1);
202         vthreads[0] = Thread.ofVirtual().start(() -> {
203             started[0].countDown();
204             TestClass.m();
205         });
206         started[0].await();
207         await(vthreads[0], Thread.State.WAITING);
208 
209         for (int i = 1; i < MAX_VTHREAD_COUNT; i++) {
210             final int id = i;
211             started[i] = new CountDownLatch(1);
212             vthreads[i] = Thread.ofVirtual().start(() -> {
213                 started[id].countDown();
214                 Driver.foo();
215             });
216         }
217         for (int i = 1; i < MAX_VTHREAD_COUNT; i++) {
218             started[i].await();
219             await(vthreads[i], Thread.State.WAITING);
220         }
221 
222         finishInvokeStatic3.countDown();
223         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
224             vthreads[i].join();
225         }
226     }
227 
228     /**
229      * Test that threads blocked waiting for klass to be initialized
230      * on new bytecode release the carrier.
231      */
232     @Test
233     void testReleaseAtKlassInitNew() throws Exception {
234         class TestClass {
235             static {
236                 try {
237                     finishNew.await();
238                 } catch(InterruptedException e) {}
239             }
240             void m() {
241             }
242         }
243 
244         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
245         CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
246         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
247             final int id = i;
248             started[i] = new CountDownLatch(1);
249             vthreads[i] = Thread.ofVirtual().start(() -> {
250                 started[id].countDown();
251                 TestClass x = new TestClass();
252                 x.m();
253             });
254         }
255         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
256             started[i].await();
257             await(vthreads[i], Thread.State.WAITING);
258         }
259 
260         finishNew.countDown();
261         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
262             vthreads[i].join();
263         }
264     }
265 
266     /**
267      * Test that threads blocked waiting for klass to be initialized
268      * on getstatic bytecode release the carrier.
269      */
270     @Test
271     void testReleaseAtKlassInitGetStatic() throws Exception {
272         class TestClass {
273             static {
274                 try {
275                     finishGetStatic.await();
276                 } catch(InterruptedException e) {}
277             }
278             public static int NUMBER = 150;
279         }
280 
281         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
282         CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
283         AtomicInteger[] result = new AtomicInteger[MAX_VTHREAD_COUNT];
284         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
285             final int id = i;
286             started[i] = new CountDownLatch(1);
287             result[i] = new AtomicInteger();
288             vthreads[i] = Thread.ofVirtual().start(() -> {
289                 started[id].countDown();
290                 result[id].set(TestClass.NUMBER);
291             });
292         }
293         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
294             started[i].await();
295             await(vthreads[i], Thread.State.WAITING);
296         }
297 
298         finishGetStatic.countDown();
299         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
300             vthreads[i].join();
301             assertEquals(result[i].get(), TestClass.NUMBER);
302         }
303     }
304 
305     /**
306      * Test that threads blocked waiting for klass to be initialized
307      * on putstatic release the carrier.
308      */
309     @Test
310     void testReleaseAtKlassInitPutStatic() throws Exception {
311         class TestClass {
312             static {
313                 try {
314                     finishPutStatic.await();
315                 } catch(InterruptedException e) {}
316             }
317             public static int NUMBER;
318         }
319 
320         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
321         CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
322         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
323             final int id = i;
324             started[i] = new CountDownLatch(1);
325             vthreads[i] = Thread.ofVirtual().start(() -> {
326                 started[id].countDown();
327                 TestClass.NUMBER = id;
328             });
329         }
330         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
331             started[i].await();
332             await(vthreads[i], Thread.State.WAITING);
333         }
334 
335         finishPutStatic.countDown();
336         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
337             vthreads[i].join();
338         }
339     }
340 
341     /**
342      * Test that interruptions during preemption on klass init
343      * are preserved.
344      */
345     @ParameterizedTest
346     @MethodSource("interruptTestCases")
347     void testReleaseAtKlassInitPreserverInterrupt(int timeout, Runnable m, CountDownLatch finish) throws Exception {
348         // Start vthread1 and wait until it blocks in TestClassX initializer
349         var vthread1_started = new CountDownLatch(1);
350         var vthread1 = Thread.ofVirtual().start(() -> {
351                 vthread1_started.countDown();
352                 m.run();
353             });
354         vthread1_started.await();
355         await(vthread1, Thread.State.WAITING);
356 
357         // Start vthread2 and wait until it gets preempted on TestClassX initialization
358         var lock = new Object();
359         var interruptedException = new AtomicBoolean();
360         var vthread2_started = new CountDownLatch(1);
361         var vthread2 = Thread.ofVirtual().start(() -> {
362                 vthread2_started.countDown();
363                 m.run();
364                 synchronized (lock) {
365                     try {
366                         if (timeout > 0) {
367                             lock.wait(timeout);
368                         } else {
369                             lock.wait();
370                         }
371                     } catch (InterruptedException e) {
372                         // check stack trace has the expected frames
373                         Set<String> expected = Set.of("wait0", "wait", "run");
374                         Set<String> methods = Stream.of(e.getStackTrace())
375                                 .map(StackTraceElement::getMethodName)
376                                 .collect(Collectors.toSet());
377                         assertTrue(methods.containsAll(expected));
378                         interruptedException.set(true);
379                     }
380                 }
381             });
382         vthread2_started.await();
383         await(vthread2, Thread.State.WAITING);
384 
385         // Interrupt vthread2 and let initialization of TestClassX finish
386         vthread2.interrupt();
387         finish.countDown();
388         vthread1.join();
389         vthread2.join();
390         assertTrue(interruptedException.get());
391     }
392 
393     static CountDownLatch finishInterrupt0 = new CountDownLatch(1);
394     class TestClass0 {
395         static {
396             try {
397                 finishInterrupt0.await();
398             } catch(InterruptedException e) {}
399         }
400         static void m() {}
401     }
402 
403     static CountDownLatch finishInterrupt30000 = new CountDownLatch(1);
404     class TestClass30000 {
405         static {
406             try {
407                 finishInterrupt30000.await();
408             } catch(InterruptedException e) {}
409         }
410         static void m() {}
411     }
412 
413     static CountDownLatch finishInterruptMax = new CountDownLatch(1);
414     class TestClassMax {
415         static {
416             try {
417                 finishInterruptMax.await();
418             } catch(InterruptedException e) {}
419         }
420         static void m() {}
421     }
422 
423     static Stream<Arguments> interruptTestCases() {
424         return Stream.of(
425             Arguments.of(0, (Runnable) TestClass0::m, finishInterrupt0),
426             Arguments.of(30000, (Runnable) TestClass30000::m, finishInterrupt30000),
427             Arguments.of(Integer.MAX_VALUE, (Runnable) TestClassMax::m, finishInterruptMax)
428         );
429     }
430 
431     /**
432      * Waits for the given thread to reach a given state.
433      */
434     private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
435         Thread.State state = thread.getState();
436         while (state != expectedState) {
437             assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
438             Thread.sleep(10);
439             state = thread.getState();
440         }
441     }
442 }