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 }