/*
 * Copyright (c) 2024, 2025, 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 id=Xint_outer_inner
 * @requires vm.flagless
 * @summary Tests recursive locking in -Xint in outer then inner mode.
 * @library /testlibrary /test/lib
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -Xint
 *     -XX:LockingMode=0
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 1
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -Xint
 *     -XX:LockingMode=1
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 1
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -Xint
 *     -XX:LockingMode=2
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 1
 */

/*
 * @test id=Xint_alternate_AB
 * @requires vm.flagless
 * @summary Tests recursive locking in -Xint in alternate A and B mode.
 * @library /testlibrary /test/lib
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -Xint
 *     -XX:LockingMode=0
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 2
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -Xint
 *     -XX:LockingMode=1
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 2
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -Xint
 *     -XX:LockingMode=2
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 2
 */

/*
 * @test id=C1_outer_inner
 * @requires vm.flagless
 * @requires vm.compiler1.enabled
 * @summary Tests recursive locking in C1 in outer then inner mode.
 * @library /testlibrary /test/lib
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -XX:TieredStopAtLevel=1
 *     -XX:LockingMode=0
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 1
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -XX:TieredStopAtLevel=1
 *     -XX:LockingMode=1
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 1
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -XX:TieredStopAtLevel=1
 *     -XX:LockingMode=2
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 1
 */

/*
 * @test id=C1_alternate_AB
 * @requires vm.flagless
 * @requires vm.compiler1.enabled
 * @summary Tests recursive locking in C1 in alternate A and B mode.
 * @library /testlibrary /test/lib
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -XX:TieredStopAtLevel=1
 *     -XX:LockingMode=0
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 2
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -XX:TieredStopAtLevel=1
 *     -XX:LockingMode=1
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 2
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -XX:TieredStopAtLevel=1
 *     -XX:LockingMode=2
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 2
 */

/*
 * @test id=C2_outer_inner
 * @requires vm.flagless
 * @requires vm.compiler2.enabled
 * @summary Tests recursive locking in C2 in outer then inner mode.
 * @library /testlibrary /test/lib
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -XX:-EliminateNestedLocks
 *     -XX:LockingMode=0
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 1
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -XX:-EliminateNestedLocks
 *     -XX:LockingMode=1
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 1
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -XX:-EliminateNestedLocks
 *     -XX:LockingMode=2
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 1
 */

/*
 * @test id=C2_alternate_AB
 * @requires vm.flagless
 * @requires vm.compiler2.enabled
 * @summary Tests recursive locking in C2 in alternate A and B mode.
 * @library /testlibrary /test/lib
 * @build jdk.test.whitebox.WhiteBox
 *
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -XX:LockingMode=0
 *     -XX:-EliminateNestedLocks
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 2
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -XX:LockingMode=1
 *     -XX:-EliminateNestedLocks
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 2
 *
 * @run main/othervm -Xbootclasspath/a:.
 *     -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
 *     -XX:LockingMode=2
 *     -XX:-EliminateNestedLocks
 *     -Xms256m -Xmx256m
 *     TestRecursiveLocking 5 2
 */

import jdk.test.lib.Asserts;
import jdk.test.whitebox.WhiteBox;
import jtreg.SkippedException;

public class TestRecursiveLocking {
    static final WhiteBox WB = WhiteBox.getWhiteBox();
    static final int flagLockingMode = WB.getIntVMFlag("LockingMode").intValue();
    static final int constLockStackCapacity = WB.getLockStackCapacity();
    static final int LM_MONITOR = 0;
    static final int LM_LEGACY = 1;
    static final int LM_LIGHTWEIGHT = 2;
    static final int def_mode = 2;
    static final int def_n_secs = 30;
    static final SyncThread syncThread = new SyncThread();

    // This SynchronizedObject class and the OUTER followed by INNER testing
    // model is adapted from runtime/lockStack/TestLockStackCapacity.java.
    static class SynchronizedObject {
        private int counter;

        synchronized void runInner(int depth, SynchronizedObject outer) {
            counter++;

            // Legacy mode has no lock stack, i.e., there is no limit
            // on recursion, so for legacy mode we can't say that
            // "outer" must be inflated here, which we can say for all
            // the other locking modes.
            if (flagLockingMode != LM_LEGACY) {
                outer.assertInflated();
            }

            // We haven't reached the stack lock capacity (recursion
            // level), so we shouldn't be inflated here. Except for
            // monitor mode, which is always inflated.
            if (flagLockingMode != LM_MONITOR) {
                assertNotInflated();
            }
            if (depth == 1) {
                return;
            } else {
                runInner(depth - 1, outer);
            }
            if (flagLockingMode != LM_MONITOR) {
                assertNotInflated();
            }
        }

        synchronized void runOuter(int depth, SynchronizedObject inner) {
            counter++;

            if (flagLockingMode != LM_MONITOR) {
                assertNotInflated();
            }
            if (depth == 1) {
                inner.runInner(constLockStackCapacity, this);
            } else {
                runOuter(depth - 1, inner);
            }
            if (flagLockingMode != LM_LEGACY) {
                assertInflated();
            }
        }

        // This test nests x recursive locks of INNER, in x recursive
        // locks of OUTER. The number x is taken from the max number
        // of elements in the lock stack.
        public void runOuterInnerTest() {
            final SynchronizedObject OUTER = new SynchronizedObject();
            final SynchronizedObject INNER = new SynchronizedObject();

            // Just checking since they are new objects:
            OUTER.assertNotInflated();
            INNER.assertNotInflated();

            synchronized (OUTER) {
                OUTER.counter++;

                if (flagLockingMode != LM_MONITOR) {
                    OUTER.assertNotInflated();
                }
                INNER.assertNotInflated();
                OUTER.runOuter(constLockStackCapacity - 1, INNER);

                if (flagLockingMode != LM_LEGACY) {
                    OUTER.assertInflated();
                }
                if (flagLockingMode != LM_MONITOR) {
                    INNER.assertNotInflated();
                }
            }

            // Verify that the nested monitors have been properly released:
            syncThread.verifyCanBeSynced(OUTER);
            syncThread.verifyCanBeSynced(INNER);

            Asserts.assertEquals(OUTER.counter, constLockStackCapacity);
            Asserts.assertEquals(INNER.counter, constLockStackCapacity);
        }

        synchronized void runA(int depth, SynchronizedObject B) {
            counter++;

            if (flagLockingMode == LM_LIGHTWEIGHT) {
                // First time we lock A, A is the only one on the lock
                // stack.
                if (counter == 1) {
                    assertNotInflated();
                } else {
                    // Second time we want to lock A, the lock stack
                    // looks like this [A, B]. Lightweight locking
                    // doesn't allow interleaving ([A, B, A]), instead
                    // it inflates A and removes it from the lock
                    // stack. Which leaves us with only [B] on the
                    // lock stack. After more recursions it will grow
                    // to [B, B ... B].
                    assertInflated();
                }
            } else if (flagLockingMode == LM_MONITOR) {
                assertInflated();
            }

            // Call runB() at the same depth as runA's depth:
            B.runB(depth, this);
        }

        synchronized void runB(int depth, SynchronizedObject A) {
            counter++;

            if (flagLockingMode != LM_MONITOR) {
                // Legacy tolerates endless recursions. While testing
                // lightweight we don't go deeper than the size of the
                // lock stack, which in this test case will be filled
                // with a number of B-elements. See comment in runA()
                // above for more info.
                assertNotInflated();
            } else {
                assertInflated();
            }

            if (depth == 1) {
                // Reached LockStackCapacity in depth so we're done.
                return;
            } else {
                A.runA(depth - 1, this);
            }
        }

        // This test alternates by locking A and B.
        public void runAlternateABTest() {
            final SynchronizedObject A = new SynchronizedObject();
            final SynchronizedObject B = new SynchronizedObject();

            // Just checking since they are new objects:
            A.assertNotInflated();
            B.assertNotInflated();

            A.runA(constLockStackCapacity, B);

            // Verify that the nested monitors have been properly released:
            syncThread.verifyCanBeSynced(A);
            syncThread.verifyCanBeSynced(B);

            Asserts.assertEquals(A.counter, constLockStackCapacity);
            Asserts.assertEquals(B.counter, constLockStackCapacity);
            if (flagLockingMode == LM_LEGACY) {
                A.assertNotInflated();
            }
            // Implied else: for LM_MONITOR or LM_LIGHTWEIGHT it can be
            // either inflated or not because A is not locked anymore
            // and subject to deflation.

            if (flagLockingMode != LM_MONITOR) {
                B.assertNotInflated();
            }
        }

        void assertNotInflated() {
            Asserts.assertFalse(WB.isMonitorInflated(this));
        }

        void assertInflated() {
            Asserts.assertTrue(WB.isMonitorInflated(this));
        }
    }

    static void usage() {
        System.err.println();
        System.err.println("Usage: java TestRecursiveLocking [n_secs]");
        System.err.println("       java TestRecursiveLocking n_secs [mode]");
        System.err.println();
        System.err.println("where:");
        System.err.println("    n_secs  ::= > 0");
        System.err.println("            Default n_secs is " + def_n_secs + ".");
        System.err.println("    mode    ::= 1 - outer and inner");
        System.err.println("            ::= 2 - alternate A and B");
        System.err.println("            Default mode is " + def_mode + ".");
        System.exit(1);
    }

    public static void main(String... argv) throws Exception {
        int mode = def_mode;
        int n_secs = def_n_secs;

        if (argv.length != 0 && argv.length != 1 && argv.length != 2) {
            usage();
        } else if (argv.length > 0) {
            try {
                n_secs = Integer.parseInt(argv[0]);
                if (n_secs <= 0) {
                    throw new NumberFormatException("Not > 0: '" + argv[0]
                                                    + "'");
                }
            } catch (NumberFormatException nfe) {
                System.err.println();
                System.err.println(nfe);
                System.err.println("ERROR: '" + argv[0]
                                   + "': invalid n_secs value.");
                usage();
            }

            if (argv.length > 1) {
                try {
                    mode = Integer.parseInt(argv[1]);
                    if (mode != 1 && mode != 2) {
                        throw new NumberFormatException("Not 1 -> 2: '"
                                                        + argv[1] + "'");
                    }
                } catch (NumberFormatException nfe) {
                    System.err.println();
                    System.err.println(nfe);
                    System.err.println("ERROR: '" + argv[1]
                                       + "': invalid mode value.");
                    usage();
                }
            }
        }

        System.out.println("INFO: LockingMode=" + flagLockingMode);
        System.out.println("INFO: LockStackCapacity=" + constLockStackCapacity);
        System.out.println("INFO: n_secs=" + n_secs);
        System.out.println("INFO: mode=" + mode);

        long loopCount = 0;
        long endTime = System.currentTimeMillis() + n_secs * 1000;

        syncThread.waitForStart();

        while (System.currentTimeMillis() < endTime) {
            loopCount++;
            SynchronizedObject syncObj = new SynchronizedObject();
            switch (mode) {
            case 1:
                syncObj.runOuterInnerTest();
                break;

            case 2:
                syncObj.runAlternateABTest();
                break;

            default:
                throw new RuntimeException("bad mode parameter: " + mode);
            }
        }

        syncThread.setDone();
        try {
            syncThread.join();
        } catch (InterruptedException ie) {
            // This should not happen.
            ie.printStackTrace();
        }

        System.out.println("INFO: main executed " + loopCount + " loops in "
                           + n_secs + " seconds.");
    }
}

class SyncThread extends Thread {
    static final boolean verbose = false;  // set to true for debugging
    private boolean done = false;
    private boolean haveWork = false;
    private Object obj;
    private Object waiter = new Object();

    public void run() {
        if (verbose) System.out.println("SyncThread: running.");
        synchronized (waiter) {
            // Let main know that we are running:
            if (verbose) System.out.println("SyncThread: notify main running.");
            waiter.notify();

            while (!done) {
                if (verbose) System.out.println("SyncThread: waiting.");
                try {
                    waiter.wait();
                } catch (InterruptedException ie) {
                    // This should not happen.
                    ie.printStackTrace();
                }
                if (haveWork) {
                    if (verbose) System.out.println("SyncThread: working.");
                    synchronized (obj) {
                    }
                    if (verbose) System.out.println("SyncThread: worked.");
                    haveWork = false;
                    waiter.notify();
                    if (verbose) System.out.println("SyncThread: notified.");
                }
                else if (verbose) {
                    System.out.println("SyncThread: notified without work.");
                }
            }
        }
        if (verbose) System.out.println("SyncThread: exiting.");
    }

    public void setDone() {
        synchronized (waiter) {
            if (verbose) System.out.println("main: set done.");
            done = true;
            waiter.notify();
        }
    }

    public void verifyCanBeSynced(Object obj) {
        synchronized (waiter) {
            if (verbose) System.out.println("main: queueing up work.");
            this.obj = obj;
            haveWork = true;
            if (verbose) System.out.println("main: notifying SyncThread.");
            waiter.notify();
            if (verbose) System.out.println("main: waiting for SyncThread.");
            while (haveWork) {
                try {
                    waiter.wait();
                } catch (InterruptedException ie) {
                    // This should not happen.
                    ie.printStackTrace();
                }
            }
            if (verbose) System.out.println("main: waited for SyncThread.");
        }
    }

    public void waitForStart() {
        synchronized (waiter) {
            this.start();

            // Wait for SyncThread to actually get running:
            if (verbose) System.out.println("main: wait for SyncThread start.");
            try {
                waiter.wait();
            } catch (InterruptedException ie) {
                // This should not happen.
                ie.printStackTrace();
            }
            if (verbose) System.out.println("main: waited for SyncThread start.");
        }
    }
}
