1 /*
  2  * Copyright (c) 2022, 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 id=default
 26  * @bug 8284161 8286788
 27  * @summary Test java.lang.management.ThreadInfo contains expected information for carrier threads
 28  * @requires vm.continuations
 29  * @modules java.base/java.lang:+open
 30  * @library /test/lib
 31  * @run junit CarrierThreadInfo
 32  */
 33 
 34 /**
 35  * @test id=LM_LIGHTWEIGHT
 36  * @requires vm.continuations
 37  * @modules java.base/java.lang:+open
 38  * @library /test/lib
 39  * @run junit/othervm -XX:LockingMode=2 CarrierThreadInfo
 40  */
 41 
 42 /**
 43  * @test id=LM_LEGACY
 44  * @requires vm.continuations
 45  * @modules java.base/java.lang:+open
 46  * @library /test/lib
 47  * @run junit/othervm -XX:LockingMode=1 CarrierThreadInfo
 48  */
 49 
 50 /**
 51  * @test id=LM_MONITOR
 52  * @requires vm.continuations
 53  * @modules java.base/java.lang:+open
 54  * @library /test/lib
 55  * @run junit/othervm -XX:LockingMode=0 CarrierThreadInfo
 56  */
 57 
 58 import java.lang.management.LockInfo;
 59 import java.lang.management.ManagementFactory;
 60 import java.lang.management.ThreadInfo;
 61 import java.lang.management.ThreadMXBean;
 62 import java.util.Arrays;
 63 import java.util.concurrent.Executor;
 64 import java.util.concurrent.Executors;
 65 import java.util.concurrent.ExecutorService;
 66 import java.util.concurrent.ThreadFactory;
 67 import java.util.concurrent.atomic.AtomicBoolean;
 68 import java.util.concurrent.atomic.AtomicReference;
 69 
 70 import jdk.test.lib.thread.VThreadScheduler;
 71 import org.junit.jupiter.api.Test;
 72 import static org.junit.jupiter.api.Assertions.*;
 73 
 74 class CarrierThreadInfo {
 75 
 76     /**
 77      * Test that ThreadInfo.getLockedMonitors returns information about a lock held by
 78      * a carrier thread.
 79      */
 80     @Test
 81     void testCarrierThreadHoldsLock() throws Exception {
 82         Object lock = new Object();
 83         ThreadFactory factory = task -> Thread.ofPlatform().unstarted(() -> {
 84             synchronized (lock) {
 85                 task.run();
 86             }
 87         });
 88 
 89         try (var scheduler = new CustomScheduler(factory)) {
 90             var started = new AtomicBoolean();
 91             var done = new AtomicBoolean();
 92             Thread vthread = scheduler.forkVirtualThread(() -> {
 93                 started.set(true);
 94                 while (!done.get()) {
 95                     Thread.onSpinWait();
 96                 }
 97             });
 98             try {
 99                 awaitTrue(started);
100 
101                 // carrier threads holds the lock
102                 long carrierId = scheduler.carrier().threadId();
103                 ThreadInfo threadInfo = ManagementFactory.getPlatformMXBean(ThreadMXBean.class)
104                         .getThreadInfo(new long[] { carrierId }, true, true)[0];
105                 boolean holdsLock = Arrays.stream(threadInfo.getLockedMonitors())
106                         .anyMatch(mi -> mi.getIdentityHashCode() == System.identityHashCode(lock));
107                 assertTrue(holdsLock, "Carrier should hold lock");
108 
109             } finally {
110                 done.set(true);
111             }
112         }
113     }
114 
115     /**
116      * Test that ThreadInfo.getLockedMonitors does not return information about a lock
117      * held by mounted virtual thread.
118      */
119     @Test
120     void testVirtualThreadHoldsLock() throws Exception {
121         ThreadFactory factory = Executors.defaultThreadFactory();
122         try (var scheduler = new CustomScheduler(factory)) {
123             var started = new AtomicBoolean();
124             var lock = new Object();
125             var done = new AtomicBoolean();
126             Thread vthread = scheduler.forkVirtualThread(() -> {
127                 started.set(true);
128                 while (!done.get()) {
129                     Thread.onSpinWait();
130                 }
131             });
132             try {
133                 awaitTrue(started);
134 
135                 // carrier threads does not hold lock
136                 long carrierId = scheduler.carrier().threadId();
137                 ThreadInfo threadInfo = ManagementFactory.getPlatformMXBean(ThreadMXBean.class)
138                         .getThreadInfo(new long[] { carrierId }, true, true)[0];
139                 boolean holdsLock = Arrays.stream(threadInfo.getLockedMonitors())
140                         .anyMatch(mi -> mi.getIdentityHashCode() == System.identityHashCode(lock));
141                 assertFalse(holdsLock, "Carrier should not hold lock");
142 
143             } finally {
144                 done.set(true);
145             }
146         }
147     }
148 
149     /**
150      * Test that ThreadInfo.getLockOwnerId and getLockInfo return information about a
151      * synthetic lock that make it appear that the carrier is blocking waiting on the
152      * virtual thread.
153      */
154     @Test
155     void testCarrierThreadWaits() throws Exception {
156         ThreadFactory factory = Executors.defaultThreadFactory();
157         try (var scheduler = new CustomScheduler(factory)) {
158             var started = new AtomicBoolean();
159             var done = new AtomicBoolean();
160             Thread vthread = scheduler.forkVirtualThread(() -> {
161                 started.set(true);
162                 while (!done.get()) {
163                     Thread.onSpinWait();
164                 }
165             });
166             try {
167                 awaitTrue(started);
168 
169                 long carrierId = scheduler.carrier().threadId();
170                 long vthreadId = vthread.threadId();
171 
172                 ThreadInfo threadInfo = ManagementFactory.getThreadMXBean().getThreadInfo(carrierId);
173                 assertNotNull(threadInfo);
174 
175                 // carrier should be blocked waiting for lock owned by virtual thread
176                 assertEquals(vthreadId, threadInfo.getLockOwnerId());
177 
178                 // carrier thread should be on blocked waiting on virtual thread
179                 LockInfo lockInfo = threadInfo.getLockInfo();
180                 assertNotNull(lockInfo);
181                 assertEquals(vthread.getClass().getName(), lockInfo.getClassName());
182                 assertEquals(System.identityHashCode(vthread), lockInfo.getIdentityHashCode());
183 
184             } finally {
185                 done.set(true);
186             }
187         }
188     }
189 
190     /**
191      * Custom scheduler with a single carrier thread.
192      */
193     private static class CustomScheduler implements AutoCloseable {
194         private final ExecutorService pool;
195         private final Executor scheduler;
196         private final AtomicReference<Thread> carrierRef = new AtomicReference<>();
197 
198         CustomScheduler(ThreadFactory factory) {
199             pool = Executors.newSingleThreadExecutor(factory);
200             scheduler = task -> {
201                 pool.submit(() -> {
202                     carrierRef.set(Thread.currentThread());
203                     try {
204                         task.run();
205                     } finally {
206                         carrierRef.set(null);
207                     }
208                 });
209             };
210         }
211 
212         /**
213          * Returns the carrier thread if a virtual thread is mounted.
214          */
215         Thread carrier() throws InterruptedException {
216             return carrierRef.get();
217         }
218 
219         /**
220          * Starts a virtual thread to execute the give task.
221          */
222         Thread forkVirtualThread(Runnable task) {
223             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
224             Thread thread = factory.newThread(task);
225             thread.start();
226             return thread;
227         }
228 
229         @Override
230         public void close() {
231             pool.close();
232         }
233     }
234 
235     /**
236      * Waits for the boolean value to become true.
237      */
238     private static void awaitTrue(AtomicBoolean ref) throws InterruptedException {
239         while (!ref.get()) {
240             Thread.sleep(20);
241         }
242     }
243 }