1 /*
  2  * Copyright (c) 2020, 2024, 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  * @bug 8284161 8290562 8303242
 27  * @summary Test java.lang.management.ThreadMXBean with virtual threads
 28  * @modules java.base/java.lang:+open java.management
 29  * @library /test/lib
 30  * @run junit/othervm VirtualThreads
 31  */
 32 
 33 /**
 34  * @test id=no-vmcontinuations
 35  * @requires vm.continuations
 36  * @modules java.base/java.lang:+open java.management
 37  * @library /test/lib
 38  * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations VirtualThreads
 39  */
 40 
 41 import java.lang.management.ManagementFactory;
 42 import java.lang.management.ThreadInfo;
 43 import java.lang.management.ThreadMXBean;
 44 import java.nio.channels.Selector;
 45 import java.util.Arrays;
 46 import java.util.Set;
 47 import java.util.concurrent.Executor;
 48 import java.util.concurrent.ExecutorService;
 49 import java.util.concurrent.Executors;
 50 import java.util.concurrent.ThreadFactory;
 51 import java.util.concurrent.atomic.AtomicReference;
 52 import java.util.concurrent.locks.LockSupport;
 53 import java.util.stream.Collectors;
 54 
 55 import jdk.test.lib.thread.VThreadPinner;
 56 import jdk.test.lib.thread.VThreadRunner;
 57 import jdk.test.lib.thread.VThreadScheduler;
 58 import org.junit.jupiter.api.Test;
 59 import org.junit.jupiter.params.ParameterizedTest;
 60 import org.junit.jupiter.params.provider.ValueSource;
 61 import static org.junit.jupiter.api.Assertions.*;
 62 import static org.junit.jupiter.api.Assumptions.*;
 63 
 64 public class VirtualThreads {
 65 
 66     /**
 67      * Test that ThreadMXBean.dumpAllThreads does not include virtual threads.
 68      */
 69     @ParameterizedTest
 70     @ValueSource(ints = {0, Integer.MAX_VALUE})
 71     void testDumpAllThreads(int maxDepth) {
 72         Thread vthread = Thread.startVirtualThread(LockSupport::park);
 73         try {
 74             ThreadMXBean bean = ManagementFactory.getThreadMXBean();
 75             ThreadInfo[] infos = bean.dumpAllThreads(false, false, maxDepth);
 76             Set<Long> tids = Arrays.stream(infos)
 77                     .map(ThreadInfo::getThreadId)
 78                     .collect(Collectors.toSet());
 79 
 80             // if current thread is a platform thread then it should be included
 81             boolean expected = !Thread.currentThread().isVirtual();
 82             assertEquals(expected, tids.contains(Thread.currentThread().threadId()));
 83 
 84             // virtual thread should not be included
 85             assertFalse(tids.contains(vthread.threadId()));
 86         } finally {
 87             LockSupport.unpark(vthread);
 88         }
 89     }
 90 
 91     /**
 92      * Test that ThreadMXBean::getAllThreadsIds does not include virtual threads.
 93      */
 94     @Test
 95     void testGetAllThreadIds() {
 96         Thread vthread = Thread.startVirtualThread(LockSupport::park);
 97         try {
 98             long[] tids = ManagementFactory.getThreadMXBean().getAllThreadIds();
 99 
100             // if current thread is a platform thread then it should be included
101             boolean expected = !Thread.currentThread().isVirtual();
102             long currentTid = Thread.currentThread().threadId();
103             assertEquals(expected, Arrays.stream(tids).anyMatch(tid -> tid == currentTid));
104 
105             // virtual thread should not be included
106             long vtid = vthread.threadId();
107             assertFalse(Arrays.stream(tids).anyMatch(tid -> tid == vtid));
108         } finally {
109             LockSupport.unpark(vthread);
110         }
111     }
112 
113     /**
114      * Test that ThreadMXBean.getThreadInfo(long, maxDepth) returns null for a virtual
115      * thread.
116      */
117     @ParameterizedTest
118     @ValueSource(ints = {0, Integer.MAX_VALUE})
119     void testGetThreadInfo1(int maxDepth) {
120         Thread vthread = Thread.startVirtualThread(LockSupport::park);
121         try {
122             long tid = vthread.threadId();
123             ThreadInfo info = ManagementFactory.getThreadMXBean().getThreadInfo(tid, maxDepth);
124             assertNull(info);
125         } finally {
126             LockSupport.unpark(vthread);
127         }
128     }
129 
130     /**
131      * Test that ThreadMXBean.getThreadInfo(long, maxDepth) returns null when invoked
132      * by a virtual thread with its own thread id.
133      */
134     @ParameterizedTest
135     @ValueSource(ints = {0, Integer.MAX_VALUE})
136     void testGetThreadInfo2(int maxDepth) throws Exception {
137         VThreadRunner.run(() -> {
138             long tid = Thread.currentThread().threadId();
139             ThreadInfo info = ManagementFactory.getThreadMXBean().getThreadInfo(tid, maxDepth);
140             assertNull(info);
141         });
142     }
143 
144     /**
145      * Test that ThreadMXBean.getThreadInfo(long[], maxDepth) returns a null ThreadInfo
146      * for elements that correspond to a virtual thread.
147      */
148     @ParameterizedTest
149     @ValueSource(ints = {0, Integer.MAX_VALUE})
150     void testGetThreadInfo3(int maxDepth) {
151         Thread vthread = Thread.startVirtualThread(LockSupport::park);
152         try {
153             long tid0 = Thread.currentThread().threadId();
154             long tid1 = vthread.threadId();
155             long[] tids = new long[] { tid0, tid1 };
156             ThreadInfo[] infos = ManagementFactory.getThreadMXBean().getThreadInfo(tids, maxDepth);
157             if (Thread.currentThread().isVirtual()) {
158                 assertNull(infos[0]);
159             } else {
160                 assertEquals(tid0, infos[0].getThreadId());
161             }
162             assertNull(infos[1]);
163         } finally {
164             LockSupport.unpark(vthread);
165         }
166     }
167 
168     /**
169      * Test that ThreadMXBean.getThreadInfo(long[], boolean, boolean, maxDepth) returns
170      * a null ThreadInfo for elements that correspond to a virtual thread.
171      */
172     @ParameterizedTest
173     @ValueSource(ints = {0, Integer.MAX_VALUE})
174     void testGetThreadInfo4(int maxDepth) {
175         Thread vthread = Thread.startVirtualThread(LockSupport::park);
176         try {
177             long tid0 = Thread.currentThread().threadId();
178             long tid1 = vthread.threadId();
179             long[] tids = new long[] { tid0, tid1 };
180             ThreadMXBean bean = ManagementFactory.getThreadMXBean();
181             ThreadInfo[] infos = bean.getThreadInfo(tids, false, false, maxDepth);
182             if (Thread.currentThread().isVirtual()) {
183                 assertNull(infos[0]);
184             } else {
185                 assertEquals(tid0, infos[0].getThreadId());
186             }
187             assertNull(infos[1]);
188         } finally {
189             LockSupport.unpark(vthread);
190         }
191     }
192 
193     /**
194      * Test ThreadMXBean.getThreadInfo(long) with the thread id of a carrier thread.
195      * The stack frames of the virtual thread should not be returned.
196      */
197     @Test
198     void testGetThreadInfoCarrierThread() throws Exception {
199         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
200         try (ExecutorService pool = Executors.newFixedThreadPool(1)) {
201             var carrierRef = new AtomicReference<Thread>();
202             Executor scheduler = (task) -> {
203                 pool.execute(() -> {
204                     carrierRef.set(Thread.currentThread());
205                     task.run();
206                 });
207             };
208             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
209 
210             // start virtual thread so carrier Thread can be captured
211             Thread thread = factory.newThread(() -> { });
212             thread.start();
213             thread.join();
214             Thread carrier = carrierRef.get();
215             assertTrue(carrier != null && !carrier.isVirtual());
216 
217             try (Selector sel = Selector.open()) {
218                 String selClassName = sel.getClass().getName();
219 
220                 // start virtual thread that blocks while pinned
221                 Thread vthread = factory.newThread(() -> {
222                     try {
223                         VThreadPinner.runPinned(sel::select);
224                     } catch (Exception e) { }
225                 });
226                 vthread.start();
227 
228                 // wait for virtual thread to block in select
229                 while (!contains(vthread.getStackTrace(), selClassName)) {
230                     Thread.sleep(20);
231                 }
232 
233                 // invoke getThreadInfo get and check the stack trace
234                 long tid = carrier.getId();
235                 ThreadInfo info = ManagementFactory.getThreadMXBean().getThreadInfo(tid, 100);
236 
237                 // should only see carrier frames
238                 StackTraceElement[] stack = info.getStackTrace();
239                 assertTrue(contains(stack, "java.util.concurrent.ThreadPoolExecutor"));
240                 assertFalse(contains(stack, selClassName));
241 
242                 // carrier should not be holding any monitors
243                 assertEquals(0, info.getLockedMonitors().length);
244             }
245         }
246     }
247 
248     /**
249      * Test that ThreadMXBean.getThreadCpuTime(long) returns -1 for a virtual thread.
250      */
251     @Test
252     void testGetThreadCpuTime1() {
253         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
254         assumeTrue(bean.isThreadCpuTimeSupported(), "Thread CPU time measurement not supported");
255 
256         Thread vthread = Thread.startVirtualThread(LockSupport::park);
257         try {
258             long tid = vthread.threadId();
259             long cpuTime = bean.getThreadCpuTime(tid);
260             assertEquals(-1L, cpuTime);
261         } finally {
262             LockSupport.unpark(vthread);
263         }
264     }
265 
266     /**
267      * Test that ThreadMXBean.getThreadCpuTime(long) returns -1 when invoked by a
268      * virtual thread with its own thread id.
269      */
270     @Test
271     void testGetThreadCpuTime2() throws Exception {
272         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
273         assumeTrue(bean.isThreadCpuTimeSupported(), "Thread CPU time measurement not supported");
274 
275         VThreadRunner.run(() -> {
276             long tid = Thread.currentThread().threadId();
277             long cpuTime = bean.getThreadCpuTime(tid);
278             assertEquals(-1L, cpuTime);
279         });
280     }
281 
282     /**
283      * Test that ThreadMXBean.getThreadUserTime(long) returns -1 for a virtual thread.
284      */
285     @Test
286     void testGetThreadUserTime1() {
287         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
288         assumeTrue(bean.isThreadCpuTimeSupported(), "Thread CPU time measurement not supported");
289 
290         Thread vthread = Thread.startVirtualThread(LockSupport::park);
291         try {
292             long tid = vthread.threadId();
293             long userTime = ManagementFactory.getThreadMXBean().getThreadUserTime(tid);
294             assertEquals(-1L, userTime);
295         } finally {
296             LockSupport.unpark(vthread);
297         }
298     }
299 
300     /**
301      * Test that ThreadMXBean.getThreadUserTime(long) returns -1 when invoked by a
302      * virtual thread with its own thread id.
303      */
304     @Test
305     void testGetThreadUserTime2() throws Exception {
306         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
307         assumeTrue(bean.isThreadCpuTimeSupported(), "Thread CPU time measurement not supported");
308 
309         VThreadRunner.run(() -> {
310             long tid = Thread.currentThread().threadId();
311             long userTime = ManagementFactory.getThreadMXBean().getThreadUserTime(tid);
312             assertEquals(-1L, userTime);
313         });
314     }
315 
316     /**
317      * Test that ThreadMXBean::isCurrentThreadCpuTimeSupported returns true when
318      * CPU time measurement for the current thread is supported.
319      */
320     @Test
321     void testIsCurrentThreadCpuTimeSupported() throws Exception {
322         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
323         assumeTrue(bean.isCurrentThreadCpuTimeSupported(),
324                 "Thread CPU time measurement for the current thread not supported");
325 
326         VThreadRunner.run(() -> {
327             assertTrue(bean.isCurrentThreadCpuTimeSupported());
328         });
329     }
330 
331     /**
332      * Test that ThreadMXBean::getCurrentThreadCpuTime returns -1 when invoked
333      * from a virtual thread.
334      */
335     @Test
336     void testGetCurrentThreadCpuTime() throws Exception {
337         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
338         assumeTrue(bean.isCurrentThreadCpuTimeSupported(),
339                 "Thread CPU time measurement for the current thread not supported");
340 
341         VThreadRunner.run(() -> {
342             assertEquals(-1L, bean.getCurrentThreadCpuTime());
343         });
344     }
345 
346     /**
347      * Test that ThreadMXBean::getCurrentThreadUserTime returns -1 when invoked
348      * from a virtual thread.
349      */
350     @Test
351     void testGetCurrentThreadUserTime() throws Exception {
352         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
353         assumeTrue(bean.isCurrentThreadCpuTimeSupported(),
354                 "Thread CPU time measurement for the current thread not supported");
355 
356         VThreadRunner.run(() -> {
357             assertEquals(-1L, bean.getCurrentThreadUserTime());
358         });
359     }
360 
361     private static boolean contains(StackTraceElement[] stack, String className) {
362         return Arrays.stream(stack)
363                 .map(StackTraceElement::getClassName)
364                 .anyMatch(className::equals);
365     }
366 }