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