1 /*
  2  * Copyright (c) 2022, 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
 26  * @summary Test Thread.holdsLock when lock held by carrier thread
 27  * @requires vm.continuations
 28  * @modules java.base/java.lang:+open
 29  * @enablePreview
 30  * @run junit HoldsLock
 31  */
 32 
 33 /**
 34  * @test
 35  * @summary Test Thread.holdsLock when lock held by carrier thread
 36  * @requires vm.continuations & vm.debug
 37  * @modules java.base/java.lang:+open
 38  * @enablePreview
 39  * @run junit/othervm -XX:+UseHeavyMonitors HoldsLock
 40  */
 41 
 42 import java.lang.management.LockInfo;
 43 import java.lang.management.ManagementFactory;
 44 import java.lang.management.ThreadInfo;
 45 import java.lang.management.ThreadMXBean;
 46 import java.util.Arrays;
 47 import java.util.concurrent.ArrayBlockingQueue;
 48 import java.util.concurrent.BlockingQueue;
 49 import java.util.concurrent.ExecutionException;
 50 import java.util.concurrent.Executor;
 51 import java.util.concurrent.RejectedExecutionException;
 52 import java.util.concurrent.ThreadFactory;
 53 import java.util.concurrent.atomic.AtomicReference;
 54 
 55 import org.junit.jupiter.api.Test;
 56 import org.junit.jupiter.api.Disabled;
 57 import static org.junit.jupiter.api.Assertions.*;
 58 import static org.junit.jupiter.api.Assumptions.*;
 59 
 60 class HoldsLock {
 61     static final Object LOCK1 = new Object();
 62     static final Object LOCK2 = new Object();
 63 
 64     @Disabled("JDK-8281642")
 65     @Test
 66     void testHoldsLock() throws Exception {
 67         var q = new ArrayBlockingQueue<Runnable>(5);
 68 
 69         Thread carrier = Thread.ofPlatform().start(() -> {
 70             synchronized (LOCK1) {
 71                 eventLoop(q);
 72             }
 73         });
 74 
 75         var ex = new AtomicReference<Throwable>();
 76         Thread vthread = spawnVirtual(ex, executor(q), () -> {
 77             assertTrue(Thread.currentThread().isVirtual());
 78             assertFalse(carrier.isVirtual());
 79 
 80             synchronized (LOCK2) {
 81                 assertTrue(Thread.holdsLock(LOCK2)); // virtual thread holds lock2
 82                 assertFalse(Thread.holdsLock(LOCK1)); // carrier thread holds lock1
 83             }
 84         });
 85 
 86         join(vthread, ex);
 87         stop(carrier);
 88     }
 89 
 90     @Test
 91     void testThreadInfo() throws Exception {
 92         assumeFalse(Thread.currentThread().isVirtual(), "Main thread must be platform thread");
 93 
 94         var q = new ArrayBlockingQueue<Runnable>(5);
 95 
 96         Thread carrier = spawnCarrier(q);
 97         Thread vthread = spawnVirtual(executor(q), () -> {
 98             synchronized (LOCK1) {
 99                 try {
100                     LOCK1.wait();
101                 } catch (InterruptedException e) {}
102             }
103         });
104 
105         while (vthread.getState() != Thread.State.WAITING) {
106             Thread.sleep(10);
107         }
108         System.out.format("%s is waiting on %s%n", vthread, LOCK1);
109         long vthreadId = vthread.getId();
110         long carrierId = carrier.getId();
111 
112         System.out.format("\t\t%s%n", LOCK1);
113         String lockAsString = LOCK1.toString();
114 
115         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
116         long[] tids = bean.getAllThreadIds();
117         boolean foundCarrier = false;
118         for (long tid : tids) {
119             ThreadInfo info = bean.getThreadInfo(tid);
120             System.out.println(info); // System.out.format("%d\t%s%n", tid, info.getThreadName());
121 
122             LockInfo lock = info.getLockInfo();
123             if (lock != null && lockAsString.equals(lock.toString())) {
124                 assert false; // should never get here
125                 assert tid == vthreadId : "Actual waiter is: " + info.getThreadName()
126                         + " vthread: " + vthread + " carrier: " + carrier;
127             }
128 
129             if (tid == carrierId) {
130                 // Carrier is WAITING on vthread
131                 assertTrue(info.getThreadState() == Thread.State.WAITING);
132                 assertEquals(vthread.getClass().getName(), info.getLockInfo().getClassName());
133                 assertTrue(info.getLockInfo().getIdentityHashCode() == System.identityHashCode(vthread));
134                 assertTrue(info.getLockOwnerId() == vthreadId);
135                 foundCarrier = true;
136             }
137         }
138         assertTrue(foundCarrier);
139 
140         stop(vthread);
141         stop(carrier);
142     }
143 
144     static Thread spawnCarrier(BlockingQueue<Runnable> q) {
145         return Thread.ofPlatform().start(() -> { eventLoop(q); });
146     }
147 
148     static Executor executor(BlockingQueue<Runnable> q) {
149         return r -> {
150             if (!q.offer(r)) throw new RejectedExecutionException();
151         };
152     }
153 
154     static void eventLoop(BlockingQueue<Runnable> q) {
155         try {
156             while (!Thread.interrupted())
157                 q.take().run();
158         } catch (InterruptedException e) {}
159     }
160 
161     static Thread spawnVirtual(Executor scheduler, Runnable task) {
162         var t = newThread(scheduler, task);
163         t.start();
164         return t;
165     }
166 
167     static Thread spawnVirtual(AtomicReference<Throwable> ex, Executor scheduler, Runnable task) {
168        var t = newThread(scheduler, () -> {
169             try {
170                 task.run();
171             } catch (Throwable x) {
172                 ex.set(x);
173             }
174         });
175         t.start();
176         return t;
177     }
178 
179     static void stop(Thread t) throws InterruptedException {
180         t.interrupt();
181         t.join();
182     }
183 
184     static void join(Thread t, AtomicReference<Throwable> ex) throws Exception {
185         t.join();
186         var ex0 = ex.get();
187         if (ex0 != null)
188             throw new ExecutionException("Thread " + t + " threw an uncaught exception.", ex0);
189     }
190 
191     static Thread newThread(Executor scheduler, Runnable task) {
192         ThreadFactory factory = ThreadBuilders.virtualThreadBuilder(scheduler).factory();
193         return factory.newThread(task);
194     }
195 }