1 /* 2 * Copyright (c) 2023, 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 26 * @bug 8337199 27 * @summary Basic test for jcmd Thread.vthread_summary 28 * @requires vm.continuations 29 * @modules jdk.jcmd 30 * @library /test/lib 31 * @run junit/othervm VThreadSummaryTest 32 */ 33 34 import java.net.InetAddress; 35 import java.net.InetSocketAddress; 36 import java.net.ServerSocket; 37 import java.net.Socket; 38 import java.net.SocketTimeoutException; 39 import java.util.List; 40 import java.util.Objects; 41 import java.util.concurrent.CompletableFuture; 42 import java.util.concurrent.Executors; 43 import java.util.concurrent.ExecutorService; 44 import java.util.concurrent.ForkJoinPool; 45 import java.util.concurrent.ForkJoinWorkerThread; 46 import java.util.concurrent.ScheduledThreadPoolExecutor; 47 import java.util.concurrent.atomic.AtomicBoolean; 48 import java.util.stream.IntStream; 49 import java.lang.management.ManagementFactory; 50 import jdk.management.VirtualThreadSchedulerMXBean; 51 52 import jdk.test.lib.dcmd.PidJcmdExecutor; 53 import jdk.test.lib.process.OutputAnalyzer; 54 import org.junit.jupiter.api.Test; 55 import static org.junit.jupiter.api.Assertions.*; 56 57 class VThreadSummaryTest { 58 59 private OutputAnalyzer jcmd() { 60 return new PidJcmdExecutor().execute("Thread.vthread_summary"); 61 } 62 63 /** 64 * Test that output includes the default scheduler and timeout schedulers. 65 */ 66 @Test 67 void testSchedulers() { 68 // ensure default scheduler are timeout schedulers are initialized 69 Thread.startVirtualThread(() -> { }); 70 71 jcmd().shouldContain("Virtual thread scheduler:") 72 .shouldContain(Objects.toIdentityString(defaultScheduler())) 73 .shouldContain("Timeout schedulers:") 74 .shouldContain("[0] " + ScheduledThreadPoolExecutor.class.getName()); 75 } 76 77 /** 78 * Test that the output includes the read and writer I/O pollers. 79 */ 80 @Test 81 void testPollers() throws Exception { 82 // do blocking I/O op on a virtual thread to ensure poller mechanism is initialized 83 try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { 84 executor.submit(() -> { 85 try (var listener = new ServerSocket()) { 86 InetAddress lb = InetAddress.getLoopbackAddress(); 87 listener.bind(new InetSocketAddress(lb, 0)); 88 listener.setSoTimeout(1000); 89 try (Socket s = listener.accept()) { 90 fail("Connection from " + s.getRemoteSocketAddress()); 91 } catch (SocketTimeoutException e) { 92 // expected 93 } 94 } 95 return null; 96 }).get(); 97 } 98 99 jcmd().shouldContain("Read I/O pollers:") 100 .shouldContain("Write I/O pollers:") 101 .shouldContain("[0] sun.nio.ch"); 102 } 103 104 /** 105 * Test that the output includes thread groupings. 106 */ 107 @Test 108 void testThreadGroupings() { 109 // ensure common pool is initialized 110 CompletableFuture.runAsync(() -> { }); 111 112 try (var pool = Executors.newFixedThreadPool(1); 113 var executor = Executors.newVirtualThreadPerTaskExecutor()) { 114 115 jcmd().shouldContain("<root>") 116 .shouldContain("ForkJoinPool.commonPool") 117 .shouldContain(Objects.toIdentityString(pool)) 118 .shouldContain(Objects.toIdentityString(executor)); 119 } 120 } 121 122 /** 123 * Test that output is truncated when there are too many thread groupings. 124 */ 125 @Test 126 void testTooManyThreadGroupings() { 127 List<ExecutorService> executors = IntStream.range(0, 1000) 128 .mapToObj(_ -> Executors.newCachedThreadPool()) 129 .toList(); 130 try { 131 jcmd().shouldContain("<root>") 132 .shouldContain("<truncated ...>"); 133 } finally { 134 executors.forEach(ExecutorService::close); 135 } 136 } 137 138 /** 139 * Returns the virtual thread default scheduler. This implementation works by finding 140 * all FJ worker threads and mapping them to their pool. VirtualThreadSchedulerMXBean 141 * is used to temporarily changing target parallelism to an "unique" value, make it 142 * possbile to find the right pool. 143 */ 144 private ForkJoinPool defaultScheduler() { 145 var done = new AtomicBoolean(); 146 Thread vthread = Thread.startVirtualThread(() -> { 147 while (!done.get()) { 148 Thread.onSpinWait(); 149 } 150 }); 151 var bean = ManagementFactory.getPlatformMXBean(VirtualThreadSchedulerMXBean.class); 152 int parallelism = bean.getParallelism(); 153 try { 154 bean.setParallelism(133); 155 return Thread.getAllStackTraces() 156 .keySet() 157 .stream() 158 .filter(ForkJoinWorkerThread.class::isInstance) 159 .map(t -> ((ForkJoinWorkerThread) t).getPool()) 160 .filter(p -> p.getParallelism() == 133) 161 .findAny() 162 .orElseThrow(); 163 } finally { 164 bean.setParallelism(parallelism); 165 done.set(true); 166 } 167 } 168 }