1 /*
  2  * Copyright (c) 2023, 2025, 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_scheduler and Thread.vthread_pollers
 28  * @requires vm.continuations
 29  * @modules jdk.jcmd
 30  * @library /test/lib
 31  * @run junit/othervm VThreadCommandsTest
 32  */
 33 
 34 /*
 35  * @test id=poller-modes
 36  * @requires (os.family == "linux") | (os.family == "mac")
 37  * @requires vm.continuations
 38  * @modules jdk.jcmd
 39  * @library /test/lib
 40  * @run junit/othervm -Djdk.pollerMode=1 VThreadCommandsTest
 41  * @run junit/othervm -Djdk.pollerMode=2 VThreadCommandsTest
 42  * @run junit/othervm -Djdk.pollerMode=3 VThreadCommandsTest
 43  */
 44 
 45 import java.net.InetAddress;
 46 import java.net.InetSocketAddress;
 47 import java.net.ServerSocket;
 48 import java.net.Socket;
 49 import java.net.SocketTimeoutException;
 50 import java.util.List;
 51 import java.util.Objects;
 52 import java.util.concurrent.Executors;
 53 import java.util.concurrent.ExecutorService;
 54 import java.util.concurrent.ForkJoinPool;
 55 import java.util.concurrent.ForkJoinWorkerThread;
 56 import java.util.concurrent.ScheduledThreadPoolExecutor;
 57 import java.util.concurrent.atomic.AtomicBoolean;
 58 import java.lang.management.ManagementFactory;
 59 import jdk.management.VirtualThreadSchedulerMXBean;
 60 
 61 import jdk.test.lib.dcmd.PidJcmdExecutor;
 62 import jdk.test.lib.process.OutputAnalyzer;
 63 import org.junit.jupiter.api.Test;
 64 import static org.junit.jupiter.api.Assertions.*;
 65 
 66 class VThreadCommandsTest {
 67 
 68     /**
 69      * Thread.vthread_scheduler
 70      */
 71     @Test
 72     void testVThreadScheduler() {
 73         // ensure default scheduler is initialized
 74         Thread.startVirtualThread(() -> { });
 75 
 76         jcmd("Thread.vthread_scheduler")
 77                 .shouldContain(Objects.toIdentityString(defaultScheduler()));
 78     }
 79 
 80     /**
 81      * Thread.vthread_pollers
 82      */
 83     @Test
 84     void testVThreadPollers() throws Exception {
 85         // do blocking I/O op on a virtual thread to ensure poller mechanism is initialized
 86         try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
 87             executor.submit(() -> {
 88                 try (var listener = new ServerSocket()) {
 89                     InetAddress lb = InetAddress.getLoopbackAddress();
 90                     listener.bind(new InetSocketAddress(lb, 0));
 91                     listener.setSoTimeout(200);
 92                     try (Socket s = listener.accept()) {
 93                         System.err.format("Connection from %s ??%n", s.getRemoteSocketAddress());
 94                     } catch (SocketTimeoutException e) {
 95                         // expected
 96                     }
 97                 }
 98                 return null;
 99             }).get();
100         }
101 
102         jcmd("Thread.vthread_pollers")
103                 .shouldContain("Read I/O pollers:")
104                 .shouldContain("Write I/O pollers:")
105                 .shouldMatch("^\\[0\\] sun\\.nio\\.ch\\..+ \\[registered = [\\d]+, owner = .+\\]$");
106     }
107 
108     private OutputAnalyzer jcmd(String cmd) {
109         return new PidJcmdExecutor().execute(cmd);
110     }
111 
112     /**
113      * Returns the virtual thread default scheduler. This implementation works by finding
114      * all FJ worker threads and mapping them to their pool. VirtualThreadSchedulerMXBean
115      * is used to temporarily changing target parallelism to an "unique" value, make it
116      * possbile to find the right pool.
117      */
118     private ForkJoinPool defaultScheduler() {
119         var done = new AtomicBoolean();
120         Thread vthread = Thread.startVirtualThread(() -> {
121             while (!done.get()) {
122                 Thread.onSpinWait();
123             }
124         });
125         var bean = ManagementFactory.getPlatformMXBean(VirtualThreadSchedulerMXBean.class);
126         int parallelism = bean.getParallelism();
127         try {
128             bean.setParallelism(133);
129             return Thread.getAllStackTraces()
130                     .keySet()
131                     .stream()
132                     .filter(ForkJoinWorkerThread.class::isInstance)
133                     .map(t -> ((ForkJoinWorkerThread) t).getPool())
134                     .filter(p -> p.getParallelism() == 133)
135                     .findAny()
136                     .orElseThrow();
137         } finally {
138             bean.setParallelism(parallelism);
139             done.set(true);
140         }
141     }
142 }