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             // current thread should be included
 82             assertTrue(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             // current thread should be included
101             long currentTid = Thread.currentThread().threadId();
102             assertTrue(Arrays.stream(tids).anyMatch(tid -> tid == currentTid));
103 
104             // virtual thread should not be included
105             long vtid = vthread.threadId();
106             assertFalse(Arrays.stream(tids).anyMatch(tid -> tid == vtid));
107         } finally {
108             LockSupport.unpark(vthread);
109         }
110     }
111 
112     /**
113      * Test that ThreadMXBean.getThreadInfo(long, maxDepth) returns null for a virtual
114      * thread.
115      */
116     @ParameterizedTest
117     @ValueSource(ints = {0, Integer.MAX_VALUE})
118     void testGetThreadInfo1(int maxDepth) {
119         Thread vthread = Thread.startVirtualThread(LockSupport::park);
120         try {
121             long tid = vthread.threadId();
122             ThreadInfo info = ManagementFactory.getThreadMXBean().getThreadInfo(tid, maxDepth);
123             assertNull(info);
124         } finally {
125             LockSupport.unpark(vthread);
126         }
127     }
128 
129     /**
130      * Test that ThreadMXBean.getThreadInfo(long, maxDepth) returns null when invoked
131      * by a virtual thread with its own thread id.
132      */
133     @ParameterizedTest
134     @ValueSource(ints = {0, Integer.MAX_VALUE})
135     void testGetThreadInfo2(int maxDepth) throws Exception {
136         VThreadRunner.run(() -> {
137             long tid = Thread.currentThread().threadId();
138             ThreadInfo info = ManagementFactory.getThreadMXBean().getThreadInfo(tid, maxDepth);
139             assertNull(info);
140         });
141     }
142 
143     /**
144      * Test that ThreadMXBean.getThreadInfo(long[], maxDepth) returns a null ThreadInfo
145      * for elements that correspond to a virtual thread.
146      */
147     @ParameterizedTest
148     @ValueSource(ints = {0, Integer.MAX_VALUE})
149     void testGetThreadInfo3(int maxDepth) {
150         Thread vthread = Thread.startVirtualThread(LockSupport::park);
151         try {
152             long tid0 = Thread.currentThread().threadId();
153             long tid1 = vthread.threadId();
154             long[] tids = new long[] { tid0, tid1 };
155             ThreadInfo[] infos = ManagementFactory.getThreadMXBean().getThreadInfo(tids, maxDepth);
156             assertEquals(tid0, infos[0].getThreadId());
157             assertNull(infos[1]);
158         } finally {
159             LockSupport.unpark(vthread);
160         }
161     }
162 
163     /**
164      * Test that ThreadMXBean.getThreadInfo(long[], boolean, boolean, maxDepth) returns
165      * a null ThreadInfo for elements that correspond to a virtual thread.
166      */
167     @ParameterizedTest
168     @ValueSource(ints = {0, Integer.MAX_VALUE})
169     void testGetThreadInfo4(int maxDepth) {
170         Thread vthread = Thread.startVirtualThread(LockSupport::park);
171         try {
172             long tid0 = Thread.currentThread().threadId();
173             long tid1 = vthread.threadId();
174             long[] tids = new long[] { tid0, tid1 };
175             ThreadMXBean bean = ManagementFactory.getThreadMXBean();
176             ThreadInfo[] infos = bean.getThreadInfo(tids, false, false, maxDepth);
177             assertEquals(tid0, infos[0].getThreadId());
178             assertNull(infos[1]);
179         } finally {
180             LockSupport.unpark(vthread);
181         }
182     }
183 
184     /**
185      * Test ThreadMXBean.getThreadInfo(long) with the thread id of a carrier thread.
186      * The stack frames of the virtual thread should not be returned.
187      */
188     @Test
189     void testGetThreadInfoCarrierThread() throws Exception {
190         assumeTrue(supportsCustomScheduler(), "No support for custom schedulers");
191         try (ExecutorService pool = Executors.newFixedThreadPool(1)) {
192             var carrierRef = new AtomicReference<Thread>();
193             Executor scheduler = (task) -> {
194                 pool.execute(() -> {
195                     carrierRef.set(Thread.currentThread());
196                     task.run();
197                 });
198             };
199 
200             // start virtual thread so carrier Thread can be captured
201             virtualThreadBuilder(scheduler).start(() -> { }).join();
202             Thread carrier = carrierRef.get();
203             assertTrue(carrier != null && !carrier.isVirtual());
204 
205             try (Selector sel = Selector.open()) {
206                 // start virtual thread that blocks in a native method
207                 virtualThreadBuilder(scheduler).start(() -> {
208                     try {
209                         sel.select();
210                     } catch (Exception e) { }
211                 });
212 
213                 // invoke getThreadInfo get and check the stack trace
214                 long tid = carrier.getId();
215                 ThreadInfo info = ManagementFactory.getThreadMXBean().getThreadInfo(tid, 100);
216 
217                 // should only see carrier frames
218                 StackTraceElement[] stack = info.getStackTrace();
219                 assertTrue(contains(stack, "java.util.concurrent.ThreadPoolExecutor"));
220                 assertFalse(contains(stack, "java.nio.channels.Selector"));
221 
222                 // carrier should not be holding any monitors
223                 assertEquals(0, info.getLockedMonitors().length);
224             }
225         }
226     }
227 
228     /**
229      * Test that ThreadMXBean.getThreadCpuTime(long) returns -1 for a virtual thread.
230      */
231     @Test
232     void testGetThreadCpuTime1() {
233         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
234         assumeTrue(bean.isThreadCpuTimeSupported(), "Thread CPU time measurement not supported");
235 
236         Thread vthread = Thread.startVirtualThread(LockSupport::park);
237         try {
238             long tid = vthread.threadId();
239             long cpuTime = bean.getThreadCpuTime(tid);
240             assertEquals(-1L, cpuTime);
241         } finally {
242             LockSupport.unpark(vthread);
243         }
244     }
245 
246     /**
247      * Test that ThreadMXBean.getThreadCpuTime(long) returns -1 when invoked by a
248      * virtual thread with its own thread id.
249      */
250     @Test
251     void testGetThreadCpuTime2() throws Exception {
252         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
253         assumeTrue(bean.isThreadCpuTimeSupported(), "Thread CPU time measurement not supported");
254 
255         VThreadRunner.run(() -> {
256             long tid = Thread.currentThread().threadId();
257             long cpuTime = bean.getThreadCpuTime(tid);
258             assertEquals(-1L, cpuTime);
259         });
260     }
261 
262     /**
263      * Test that ThreadMXBean.getThreadUserTime(long) returns -1 for a virtual thread.
264      */
265     @Test
266     void testGetThreadUserTime1() {
267         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
268         assumeTrue(bean.isThreadCpuTimeSupported(), "Thread CPU time measurement not supported");
269 
270         Thread vthread = Thread.startVirtualThread(LockSupport::park);
271         try {
272             long tid = vthread.threadId();
273             long userTime = ManagementFactory.getThreadMXBean().getThreadUserTime(tid);
274             assertEquals(-1L, userTime);
275         } finally {
276             LockSupport.unpark(vthread);
277         }
278     }
279 
280     /**
281      * Test that ThreadMXBean.getThreadUserTime(long) returns -1 when invoked by a
282      * virtual thread with its own thread id.
283      */
284     @Test
285     void testGetThreadUserTime2() throws Exception {
286         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
287         assumeTrue(bean.isThreadCpuTimeSupported(), "Thread CPU time measurement not supported");
288 
289         VThreadRunner.run(() -> {
290             long tid = Thread.currentThread().threadId();
291             long userTime = ManagementFactory.getThreadMXBean().getThreadUserTime(tid);
292             assertEquals(-1L, userTime);
293         });
294     }
295 
296     /**
297      * Test that ThreadMXBean::isCurrentThreadCpuTimeSupported returns true when
298      * CPU time measurement for the current thread is supported.
299      */
300     @Test
301     void testIsCurrentThreadCpuTimeSupported() throws Exception {
302         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
303         assumeTrue(bean.isCurrentThreadCpuTimeSupported(),
304                 "Thread CPU time measurement for the current thread not supported");
305 
306         VThreadRunner.run(() -> {
307             assertTrue(bean.isCurrentThreadCpuTimeSupported());
308         });
309     }
310 
311     /**
312      * Test that ThreadMXBean::getCurrentThreadCpuTime returns -1 when invoked
313      * from a virtual thread.
314      */
315     @Test
316     void testGetCurrentThreadCpuTime() throws Exception {
317         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
318         assumeTrue(bean.isCurrentThreadCpuTimeSupported(),
319                 "Thread CPU time measurement for the current thread not supported");
320 
321         VThreadRunner.run(() -> {
322             assertEquals(-1L, bean.getCurrentThreadCpuTime());
323         });
324     }
325 
326     /**
327      * Test that ThreadMXBean::getCurrentThreadUserTime returns -1 when invoked
328      * from a virtual thread.
329      */
330     @Test
331     void testGetCurrentThreadUserTime() throws Exception {
332         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
333         assumeTrue(bean.isCurrentThreadCpuTimeSupported(),
334                 "Thread CPU time measurement for the current thread not supported");
335 
336         VThreadRunner.run(() -> {
337             assertEquals(-1L, bean.getCurrentThreadUserTime());
338         });
339     }
340 
341     private static boolean contains(StackTraceElement[] stack, String className) {
342         return Arrays.stream(stack)
343                 .map(StackTraceElement::getClassName)
344                 .anyMatch(className::equals);
345     }
346 
347     /**
348      * Returns a builder to create virtual threads that use the given scheduler.
349      * @throws UnsupportedOperationException if there is no support for custom schedulers
350      */
351     private static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) {
352         Thread.Builder.OfVirtual builder = Thread.ofVirtual();
353         try {
354             Class<?> clazz = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder");
355             Constructor<?> ctor = clazz.getDeclaredConstructor(Executor.class);
356             ctor.setAccessible(true);
357             return (Thread.Builder.OfVirtual) ctor.newInstance(scheduler);
358         } catch (InvocationTargetException e) {
359             Throwable cause = e.getCause();
360             if (cause instanceof RuntimeException re) {
361                 throw re;
362             }
363             throw new RuntimeException(e);
364         } catch (Exception e) {
365             throw new RuntimeException(e);
366         }
367     }
368 
369     /**
370      * Return true if custom schedulers are supported.
371      */
372     private static boolean supportsCustomScheduler() {
373         try (var pool = Executors.newCachedThreadPool()) {
374             try {
375                 virtualThreadBuilder(pool);
376                 return true;
377             } catch (UnsupportedOperationException e) {
378                 return false;
379             }
380         }
381     }
382 }