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 }