/* * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @bug 8247972 * * @summary converted from VM Testbase nsk/jvmti/GetObjectMonitorUsage/objmonusage003 * DESCRIPTION * The test checks if the JVMTI function GetObjectMonitorUsage returns * the expected values for the owner, entry_count, waiter_count * fields of JVMTI_monitor_info. * The testcases are the following: * - unowned object without any threads waiting * - unowned object with threads waiting to be notified * - owned object without any threads waiting * - owned object with N threads waiting to enter the monitor * - owned object with N threads waiting to be notified * - owned object with N threads waiting to enter, from 0 to N threads * waiting to re-enter, from N to 0 threads waiting to be notified * - all the above scenarios are executed with platform and virtual threads * @requires vm.jvmti * @run main/othervm/native * -Djdk.virtualThreadScheduler.parallelism=10 * -agentlib:ObjectMonitorUsage ObjectMonitorUsage */ public class ObjectMonitorUsage { final static int NUMBER_OF_ENTERING_THREADS = 4; final static int NUMBER_OF_WAITING_THREADS = 4; final static int NUMBER_OF_THREADS = NUMBER_OF_ENTERING_THREADS + NUMBER_OF_WAITING_THREADS; static Object lockCheck = new Object(); native static int getRes(); native static int setTestedMonitor(Object monitor); native static void ensureBlockedOnEnter(Thread thread); native static void ensureWaitingToBeNotified(Thread thread); native static void check(Object obj, Thread owner, int entryCount, int waiterCount, int notifyWaiterCount); static void log(String msg) { System.out.println(msg); } static String vtag(boolean isVirtual) { return isVirtual ? "virtual" : "platform"; } static void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { // ignore } } static Thread startTask(int idx, TestTask task, boolean isVirtual, String kind) { Thread thread = isVirtual ? Thread.ofVirtual().name(kind + "VT" + idx).start(task) : Thread.ofPlatform().name(kind + "PT" + idx).start(task); task.setName(thread.getName()); task.waitReady(); return thread; } static Thread[] startWaitingThreads(boolean isVirtual) { Thread[] threads = new Thread[NUMBER_OF_WAITING_THREADS]; for (int i = 0; i < NUMBER_OF_WAITING_THREADS; i++) { // the WaitingTask has to wait to be notified in lockCheck.wait() Thread thread = startTask(i, new WaitingTask(), isVirtual, "Waiting"); ensureWaitingToBeNotified(thread); threads[i] = thread; } return threads; } static Thread[] startEnteringThreads(boolean isVirtual) { Thread[] threads = new Thread[NUMBER_OF_ENTERING_THREADS]; for (int i = 0; i < NUMBER_OF_ENTERING_THREADS; i++) { // the EnteringTask has to be blocked at the lockCheck enter Thread thread = startTask(i, new EnteringTask(), isVirtual, "Entering"); ensureBlockedOnEnter(thread); threads[i] = thread; } return threads; } static void joinThreads(Thread[] threads) { try { for (Thread t : threads) { t.join(); } } catch (InterruptedException e) { throw new Error("Unexpected " + e); } } /* Scenario #0: * - owning: 0 * - entering: 0 * - re-entering: 0 * - to be notified: N */ static void test0(boolean isVirtual) { String vtag = vtag(isVirtual); log("\n### test0: started " + vtag); setTestedMonitor(lockCheck); Thread[] wThreads = startWaitingThreads(isVirtual); // entry count: 0 // count of threads waiting to enter: 0 // count of threads waiting to re-enter: 0 // count of threads waiting to be notified: NUMBER_OF_WAITING_THREADS check(lockCheck, null, 0, // no owner thread 0, // count of threads waiting to enter: 0 NUMBER_OF_WAITING_THREADS); synchronized (lockCheck) { lockCheck.notifyAll(); } joinThreads(wThreads); setTestedMonitor(null); log("### test0: finished " + vtag); } /* Scenario #1: * - owning: 1 * - entering: N * - re-entering: 0 * - to be notified: 0 */ static void test1(boolean isVirtual) { String vtag = vtag(isVirtual); log("\n### test1: started " + vtag); setTestedMonitor(lockCheck); Thread[] eThreads = null; synchronized (lockCheck) { // entry count: 1 // count of threads waiting to enter: 0 // count of threads waiting to re-enter: 0 // count of threads waiting to be notified: 0 check(lockCheck, Thread.currentThread(), 1, 0, 0); eThreads = startEnteringThreads(isVirtual); // entry count: 1 // count of threads waiting to enter: NUMBER_OF_ENTERING_THREADS // count of threads waiting to re-enter: 0 // count of threads waiting to be notified: 0 check(lockCheck, Thread.currentThread(), 1, NUMBER_OF_ENTERING_THREADS, 0 /* count of threads waiting to be notified: 0 */); } joinThreads(eThreads); setTestedMonitor(null); log("### test1: finished " + vtag); } /* Scenario #2: * - owning: 1 * - entering: N * - re-entering: 0 * - to be notified: N */ static void test2(boolean isVirtual) throws Error { String vtag = vtag(isVirtual); log("\n### test2: started " + vtag); setTestedMonitor(lockCheck); Thread[] wThreads = startWaitingThreads(isVirtual); Thread[] eThreads = null; synchronized (lockCheck) { eThreads = startEnteringThreads(isVirtual); // entry count: 1 // count of threads waiting to enter: NUMBER_OF_ENTERING_THREADS // count of threads waiting to re-enter: 0 // count of threads waiting to be notified: NUMBER_OF_WAITING_THREADS check(lockCheck, Thread.currentThread(), 1, NUMBER_OF_ENTERING_THREADS, NUMBER_OF_WAITING_THREADS); lockCheck.notifyAll(); } joinThreads(wThreads); joinThreads(eThreads); setTestedMonitor(null); log("### test2: finished " + vtag); } /* Scenario #3: * Initially we have: * - owning: 1 * - entering: 0 * - re-entering: 0 * - to be notified: N * * The threads waiting to be notified are being notified one-by-one * until all threads are blocked on re-entering the monitor. * The numbers of entering/re-entering and waiting threads are checked * for correctness after each notification. */ static void test3(boolean isVirtual) throws Error { String vtag = vtag(isVirtual); log("\n### test3: started " + vtag); setTestedMonitor(lockCheck); Thread[] wThreads = startWaitingThreads(isVirtual); Thread[] eThreads = null; synchronized (lockCheck) { // entry count: 1 // count of threads waiting to enter: 0 // count of threads waiting to re-enter: 0 // count of threads waiting to be notified: NUMBER_OF_WAITING_THREADS check(lockCheck, Thread.currentThread(), 1, 0, // number of threads waiting to enter or re-enter NUMBER_OF_WAITING_THREADS); eThreads = startEnteringThreads(isVirtual); // entry count: 1 // count of threads waiting to enter: NUMBER_OF_ENTERING_THREADS // count of threads waiting to re-enter: 0 // count of threads waiting to be notified: NUMBER_OF_WAITING_THREADS check(lockCheck, Thread.currentThread(), 1, NUMBER_OF_ENTERING_THREADS, NUMBER_OF_WAITING_THREADS); for (int i = 0; i < NUMBER_OF_WAITING_THREADS; i++) { lockCheck.notify(); // notify waiting threads one by one // now the notified WaitingTask has to be blocked on the lockCheck re-enter // entry count: 1 // count of threads waiting to enter: NUMBER_OF_ENTERING_THREADS // count of threads waiting to re-enter: i + 1 // count of threads waiting to be notified: NUMBER_OF_WAITING_THREADS - i - 1 check(lockCheck, Thread.currentThread(), 1, NUMBER_OF_ENTERING_THREADS + i + 1, NUMBER_OF_WAITING_THREADS - i - 1); } } joinThreads(wThreads); joinThreads(eThreads); setTestedMonitor(null); log("### test3: finished " + vtag); } static void test(boolean isVirtual) { test0(isVirtual); test1(isVirtual); test2(isVirtual); test3(isVirtual); } public static void main(String args[]) { log("\n### main: started\n"); check(lockCheck, null, 0, 0, 0); test(false); // test platform threads test(true); // test virtual threads check(lockCheck, null, 0, 0, 0); if (getRes() > 0) { throw new RuntimeException("Failed status returned from the agent"); } log("\n### main: finished\n"); } static abstract class TestTask implements Runnable { volatile boolean ready = false; String name; public abstract void run(); String getName() { return name; } void setName(String name) { this.name = name; } void waitReady() { try { while (!ready) { Thread.sleep(10); } } catch (InterruptedException e) { throw new Error("Unexpected " + e); } } } static class EnteringTask extends TestTask { public void run() { ready = true; synchronized (lockCheck) { } } } static class WaitingTask extends TestTask { public void run() { synchronized (lockCheck) { try { ready = true; // no protection against spurious wakeups here lockCheck.wait(); } catch (InterruptedException e) { throw new Error("Unexpected " + e); } } } } }