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 }