1 /* 2 * Copyright (c) 2021, 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 import jdk.test.lib.Asserts; 24 import jdk.test.lib.Utils; 25 import jdk.test.lib.process.ProcessTools; 26 import jdk.test.lib.process.OutputAnalyzer; 27 import jdk.test.lib.thread.VThreadScheduler; 28 29 import java.lang.reflect.Constructor; 30 import java.lang.reflect.InvocationTargetException; 31 import java.util.List; 32 import java.util.concurrent.atomic.AtomicReference; 33 import java.util.concurrent.ExecutorService; 34 import java.util.concurrent.Executor; 35 import java.util.concurrent.Executors; 36 import java.util.concurrent.ThreadFactory; 37 import java.util.regex.Matcher; 38 import java.util.regex.Pattern; 39 40 /* 41 * Tests that JNI monitors work correctly with virtual threads, 42 * There are multiple test scenarios that we check using unified logging output 43 * (both positive and negative tests). Each test case is handled by its own @-test 44 * definition so that we can run each sub-test independently. 45 * 46 * The original bug was only discovered because the ForkJoinPool worker thread terminated 47 * and trigerred an assertion failure. So we use a custom scheduler to give us control. 48 */ 49 50 /** 51 * @test id=normal 52 * @bug 8327743 53 * @summary Normal lock then unlock 54 * @library /test/lib 55 * @modules java.base/java.lang:+open 56 * @requires vm.continuations 57 * @run driver JNIMonitor Normal 58 */ 59 60 /** 61 * @test id=multiNormal 62 * @bug 8327743 63 * @summary Normal lock then unlock by multiple threads 64 * @library /test/lib 65 * @modules java.base/java.lang:+open 66 * @requires vm.continuations 67 * @run driver JNIMonitor MultiNormal 68 */ 69 70 /** 71 * @test id=missingUnlock 72 * @bug 8327743 73 * @summary Don't do the unlock and exit normally 74 * @library /test/lib 75 * @modules java.base/java.lang:+open 76 * @requires vm.continuations 77 * @run driver JNIMonitor MissingUnlock 78 */ 79 80 /** 81 * @test id=multiMissingUnlock 82 * @bug 8327743 83 * @summary Don't do the unlock and exit normally, by multiple threads 84 * @library /test/lib 85 * @modules java.base/java.lang:+open 86 * @requires vm.continuations 87 * @run driver JNIMonitor MultiMissingUnlock 88 */ 89 90 /** 91 * @test id=missingUnlockWithThrow 92 * @bug 8327743 93 * @summary Don't do the unlock and exit by throwing 94 * @library /test/lib 95 * @modules java.base/java.lang:+open 96 * @requires vm.continuations 97 * @run driver JNIMonitor MissingUnlockWithThrow 98 */ 99 100 /** 101 * @test id=multiMissingUnlockWithThrow 102 * @bug 8327743 103 * @summary Don't do the unlock and exit by throwing, by multiple threads 104 * @library /test/lib 105 * @modules java.base/java.lang:+open 106 * @requires vm.continuations 107 * @run driver JNIMonitor MultiMissingUnlockWithThrow 108 */ 109 110 public class JNIMonitor { 111 112 public static void main(String[] args) throws Exception { 113 String test = args[0]; 114 String[] cmdArgs = new String[] { 115 "-Djava.library.path=" + Utils.TEST_NATIVE_PATH, 116 // Need to open java.lang to use VThreadScheduler.virtualThreadBuilder 117 "--add-opens=java.base/java.lang=ALL-UNNAMED", 118 // Enable the JNI warning 119 "-Xcheck:jni", 120 "-Xlog:jni=debug", 121 // Enable thread termination logging as a visual cross-check 122 "-Xlog:thread+os=info", 123 // We only count monitors in LM_LEGACY mode 124 "-XX:LockingMode=1", 125 // Disable compact headers since that switches locking mode to LM_LIGHTWEIGHT 126 "-XX:-UseCompactObjectHeaders", 127 "JNIMonitor$" + test, 128 }; 129 OutputAnalyzer oa = ProcessTools.executeTestJava(cmdArgs); 130 oa.shouldHaveExitValue(0); 131 oa.stdoutShouldMatch(terminated); 132 133 switch(test) { 134 case "Normal": 135 case "MultiNormal": 136 oa.stdoutShouldNotMatch(stillLocked); 137 break; 138 case "MissingUnlock": 139 oa.stdoutShouldMatch(stillLocked); 140 break; 141 case "MultiMissingUnlock": 142 parseOutputForPattern(oa.stdoutAsLines(), stillLocked, MULTI_THREAD_COUNT); 143 break; 144 case "MissingUnlockWithThrow": 145 oa.stdoutShouldMatch(stillLocked); 146 oa.stderrShouldContain(throwMsg); 147 break; 148 case "MultiMissingUnlockWithThrow": 149 parseOutputForPattern(oa.stdoutAsLines(), stillLocked, MULTI_THREAD_COUNT); 150 parseOutputForPattern(oa.stderrAsLines(), throwMsg, MULTI_THREAD_COUNT); 151 break; 152 153 default: throw new Error("Unknown arg: " + args[0]); 154 } 155 oa.reportDiagnosticSummary(); 156 } 157 158 // The number of threads for a multi tests. Arbitrarily chosen to be > 1 but small 159 // enough to not waste too much time. 160 static final int MULTI_THREAD_COUNT = 5; 161 162 // The logging message for leaving a monitor JNI locked has the form 163 // [0.187s][debug][jni] VirtualThread (tid: 28, carrier id: 29) exiting with Objects still locked by JNI MonitorEnter. 164 // but if the test is run with other logging options then whitespace may get introduced in the 165 // log decorator sections, so ignore those. 166 static final String stillLocked = "VirtualThread \\(tid:.*exiting with Objects still locked by JNI MonitorEnter"; 167 // The carrier thread termination logging has the form: 168 // [1.394s][info][os,thread] JavaThread exiting (name: "pool-1-thread-1", tid: 3090592). 169 static final String terminated = "JavaThread exiting \\(name: \"pool-1-thread-1\""; 170 171 static final String throwMsg = "Terminating via exception as requested"; 172 173 // Check the process logging output for the given pattern to see if the expected number of 174 // lines are found. 175 private static void parseOutputForPattern(List<String> lines, String pattern, int expected) { 176 Pattern p = Pattern.compile(pattern); 177 int found = 0; 178 for (String line : lines) { 179 Matcher m = p.matcher(line); 180 if (m.find()) { 181 found++; 182 } 183 } 184 if (found != expected) { 185 throw new RuntimeException("Checking for pattern \"" + pattern + "\": expected " 186 + expected + " but found " + found); 187 } 188 } 189 190 191 // straight-forward interface to JNI monitor functions 192 static native int monitorEnter(Object o); 193 static native int monitorExit(Object o); 194 195 // Isolate the native library loading to the actual test cases, not the class that 196 // jtreg Driver will load and execute. 197 static class TestBase { 198 199 static { 200 System.loadLibrary("JNIMonitor"); 201 } 202 203 static void runTest(int nThreads, boolean skipUnlock, boolean throwOnExit) throws Throwable { 204 final Object[] monitors = new Object[nThreads]; 205 for (int i = 0; i < nThreads; i++) { 206 monitors[i] = new Object(); 207 } 208 final AtomicReference<Throwable> exception = new AtomicReference(); 209 // Ensure all our VT's operate of the same carrier, sequentially. 210 // This gives us a way to control the scheduler used for our virtual threads. The test 211 // only works as intended when the virtual threads run on the same carrier thread (as 212 // that carrier maintains ownership of the monitor if the virtual thread fails to unlock it). 213 // The original issue was also only discovered due to the carrier thread terminating 214 // unexpectedly, so we can force that condition too by shutting down our custom scheduler. 215 ExecutorService scheduler = Executors.newSingleThreadExecutor(); 216 ThreadFactory factory = VThreadScheduler.virtualThreadBuilder(scheduler).factory(); 217 for (int i = 0 ; i < nThreads; i++) { 218 Object monitor = skipUnlock ? monitors[i] : monitors[0]; 219 Thread th = factory.newThread(() -> { 220 try { 221 int res = monitorEnter(monitor); 222 Asserts.assertTrue(res == 0, "monitorEnter should return 0."); 223 Asserts.assertTrue(Thread.holdsLock(monitor), "monitor should be owned"); 224 Thread.yield(); 225 if (!skipUnlock) { 226 res = monitorExit(monitor); 227 Asserts.assertTrue(res == 0, "monitorExit should return 0."); 228 Asserts.assertFalse(Thread.holdsLock(monitor), "monitor should be unowned"); 229 } 230 } catch (Throwable t) { 231 exception.set(t); 232 } 233 if (throwOnExit) { 234 throw new RuntimeException(throwMsg); 235 } 236 }); 237 th.start(); 238 th.join(); 239 if (exception.get() != null) { 240 throw exception.get(); 241 } 242 } 243 // Now force carrier thread to shutdown. 244 scheduler.shutdown(); 245 } 246 } 247 248 // These are the actual test case classes that get exec'd. 249 250 static class Normal extends TestBase { 251 public static void main(String[] args) throws Throwable { 252 runTest(1, false, false); 253 } 254 } 255 256 static class MultiNormal extends TestBase { 257 public static void main(String[] args) throws Throwable { 258 runTest(MULTI_THREAD_COUNT, false, false); 259 } 260 } 261 262 static class MissingUnlock extends TestBase { 263 public static void main(String[] args) throws Throwable { 264 runTest(1, true, false); 265 } 266 } 267 268 static class MultiMissingUnlock extends TestBase { 269 public static void main(String[] args) throws Throwable { 270 runTest(MULTI_THREAD_COUNT, true, false); 271 } 272 } 273 274 static class MissingUnlockWithThrow extends TestBase { 275 public static void main(String[] args) throws Throwable { 276 runTest(1, true, true); 277 } 278 } 279 280 static class MultiMissingUnlockWithThrow extends TestBase { 281 public static void main(String[] args) throws Throwable { 282 runTest(MULTI_THREAD_COUNT, true, true); 283 } 284 } 285 286 }