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 }