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 
 59 class HoldsLock {
 60     static final Object LOCK1 = new Object();
 61     static final Object LOCK2 = new Object();
 62 
 63     @Disabled("JDK-8281642")
 64     @Test
 65     void testHoldsLock() throws Exception {
 66         var q = new ArrayBlockingQueue<Runnable>(5);
 67 
 68         Thread carrier = Thread.ofPlatform().start(() -> {
 69             synchronized (LOCK1) {
 70                 eventLoop(q);
 71             }
 72         });
 73 
 74         var ex = new AtomicReference<Throwable>();
 75         Thread vthread = spawnVirtual(ex, executor(q), () -> {
 76             assertTrue(Thread.currentThread().isVirtual());
 77             assertFalse(carrier.isVirtual());
 78 
 79             synchronized (LOCK2) {
 80                 assertTrue(Thread.holdsLock(LOCK2)); // virtual thread holds lock2
 81                 assertFalse(Thread.holdsLock(LOCK1)); // carrier thread holds lock1
 82             }
 83         });
 84 
 85         join(vthread, ex);
 86         stop(carrier);
 87     }
 88 
 89     @Test
 90     void testThreadInfo() throws Exception {
 91         var q = new ArrayBlockingQueue<Runnable>(5);
 92 
 93         Thread carrier = spawnCarrier(q);
 94         Thread vthread = spawnVirtual(executor(q), () -> {
 95             synchronized (LOCK1) {
 96                 try {
 97                     LOCK1.wait();
 98                 } catch (InterruptedException e) {}
 99             }
100         });
101 
102         while (vthread.getState() != Thread.State.WAITING) {
103             Thread.sleep(10);
104         }
105         System.out.format("%s is waiting on %s%n", vthread, LOCK1);
106         long vthreadId = vthread.getId();
107         long carrierId = carrier.getId();
108 
109         System.out.format("\t\t%s%n", LOCK1);
110         String lockAsString = LOCK1.toString();
111 
112         ThreadMXBean bean = ManagementFactory.getThreadMXBean();
113         long[] tids = bean.getAllThreadIds();
114         boolean foundCarrier = false;
115         for (long tid : tids) {
116             ThreadInfo info = bean.getThreadInfo(tid);
117             System.out.println(info); // System.out.format("%d\t%s%n", tid, info.getThreadName());
118 
119             LockInfo lock = info.getLockInfo();
120             if (lock != null && lockAsString.equals(lock.toString())) {
121                 assert false; // should never get here
122                 assert tid == vthreadId : "Actual waiter is: " + info.getThreadName()
123                         + " vthread: " + vthread + " carrier: " + carrier;
124             }
125 
126             if (tid == carrierId) {
127                 // Carrier is WAITING on vthread
128                 assertTrue(info.getThreadState() == Thread.State.WAITING);
129                 assertEquals(vthread.getClass().getName(), info.getLockInfo().getClassName());
130                 assertTrue(info.getLockInfo().getIdentityHashCode() == System.identityHashCode(vthread));
131                 assertTrue(info.getLockOwnerId() == vthreadId);
132                 foundCarrier = true;
133             }
134         }
135         assertTrue(foundCarrier);
136 
137         stop(vthread);
138         stop(carrier);
139     }
140 
141     static Thread spawnCarrier(BlockingQueue<Runnable> q) {
142         return Thread.ofPlatform().start(() -> { eventLoop(q); });
143     }
144 
145     static Executor executor(BlockingQueue<Runnable> q) {
146         return r -> {
147             if (!q.offer(r)) throw new RejectedExecutionException();
148         };
149     }
150 
151     static void eventLoop(BlockingQueue<Runnable> q) {
152         try {
153             while (!Thread.interrupted())
154                 q.take().run();
155         } catch (InterruptedException e) {}
156     }
157 
158     static Thread spawnVirtual(Executor scheduler, Runnable task) {
159         var t = newThread(scheduler, task);
160         t.start();
161         return t;
162     }
163 
164     static Thread spawnVirtual(AtomicReference<Throwable> ex, Executor scheduler, Runnable task) {
165        var t = newThread(scheduler, () -> {
166             try {
167                 task.run();
168             } catch (Throwable x) {
169                 ex.set(x);
170             }
171         });
172         t.start();
173         return t;
174     }
175 
176     static void stop(Thread t) throws InterruptedException {
177         t.interrupt();
178         t.join();
179     }
180 
181     static void join(Thread t, AtomicReference<Throwable> ex) throws Exception {
182         t.join();
183         var ex0 = ex.get();
184         if (ex0 != null)
185             throw new ExecutionException("Thread " + t + " threw an uncaught exception.", ex0);
186     }
187 
188     static Thread newThread(Executor scheduler, Runnable task) {
189         ThreadFactory factory = ThreadBuilders.virtualThreadBuilder(scheduler).factory();
190         return factory.newThread(task);
191     }
192 }