/*
* Copyright (c) 2003, 2010, 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 4530538
* @summary Basic unit test of ThreadInfo.getLockName()
* and ThreadInfo.getLockOwnerName()
* @author Mandy Chung
*
* @build ThreadExecutionSynchronizer
* @run main/othervm Locks
*/
import java.lang.management.*;
public class Locks {
private static Object objA = new Object();
private static Object objB = new Object();
private static Object objC = new Object();
private static ThreadMXBean tm = ManagementFactory.getThreadMXBean();
private static boolean testFailed = false;
private static String getLockName(Object lock) {
if (lock == null) return null;
return lock.getClass().getName() + '@' +
Integer.toHexString(System.identityHashCode(lock));
}
private static void checkBlockedObject(Thread t, Object lock, Thread owner,
Thread.State expectedState) {
ThreadInfo info = tm.getThreadInfo(t.getId());
String result = info.getLockName();
String expectedLock = (lock != null ? getLockName(lock) : null);
String expectedOwner = (owner != null ? owner.getName() : null);
if (lock != null) {
if (expectedState ==Thread.State.BLOCKED) {
int retryCount=0;
while(info.getThreadState() != Thread.State.BLOCKED) {
if (retryCount++ > 500) {
throw new RuntimeException("Thread " + t.getName() +
" is expected to block on " + expectedLock +
" but got " + result +
" Thread.State = " + info.getThreadState());
}
goSleep(100);
}
}
if (expectedState == Thread.State.WAITING &&
info.getThreadState() != Thread.State.WAITING) {
throw new RuntimeException("Thread " + t.getName() +
" is expected to wait on " + expectedLock +
" but got " + result +
" Thread.State = " + info.getThreadState());
}
}
if ((result != null && !result.equals(expectedLock)) ||
(result == null && expectedLock != null)) {
throw new RuntimeException("Thread " + t.getName() + " is blocked on " +
expectedLock + " but got " + result);
}
result = info.getLockOwnerName();
if ((result != null && !result.equals(expectedOwner)) ||
(result == null && expectedOwner != null)) {
throw new RuntimeException("Owner of " + lock + " should be " +
expectedOwner + " but got " + result);
}
}
private static void goSleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
testFailed = true;
}
}
static ThreadExecutionSynchronizer thrsync = new ThreadExecutionSynchronizer();
static ThreadExecutionSynchronizer thrsync1 = new ThreadExecutionSynchronizer();
static class LockAThread extends Thread {
public LockAThread() {
super("LockAThread");
}
public void run() {
synchronized(objA) {
// stop here for LockBThread to hold objB
thrsync.waitForSignal();
System.out.println("LockAThread about to block on objB");
synchronized(objB) {};
}
System.out.println("LockAThread about to exit");
// The state could be anything. The expected state value
// passed with this method is not verified.
checkBlockedObject(this, null, null, Thread.State.TERMINATED);
}
}
static class LockBThread extends Thread {
public LockBThread() {
super("LockBThread");
}
public void run() {
synchronized(objB) {
// signal waiting LockAThread.
thrsync.signal();
System.out.println("LockBThread about to block on objC");
// Signal main thread about to block on objC
thrsync1.signal();
synchronized(objC) {};
}
System.out.println("LockBThread about to exit");
// The state could be anything. The expected state value
// passed with this method is not verified.
checkBlockedObject(this, null, null, Thread.State.TERMINATED);
}
public void aboutToLockC() {
// Stop here till LockBThread about to blocked
// for lock objC.
thrsync1.waitForSignal();
goSleep(500);
}
}
private static WaitingThread waiter;
private static Object ready = new Object();
private static CheckerThread checker;
static class WaitingThread extends Thread {
public WaitingThread() {
super("WaitingThread");
}
public void run() {
synchronized(objC) {
System.out.println("WaitingThread about to wait on objC");
try {
// Signal checker thread, about to wait on objC.
thrsync.signal();
objC.wait();
} catch (InterruptedException e) {
e.printStackTrace();
testFailed = true;
}
// block until CheckerThread finishes checking
System.out.println("WaitingThread about to block on ready");
// signal checker thread that it is about acquire
// object ready.
thrsync.signal();
synchronized(ready) {};
}
synchronized(objC) {
try {
// signal checker thread, about to wait on objC
thrsync.signal();
objC.wait();
} catch (InterruptedException e) {
e.printStackTrace();
testFailed = true;
}
}
System.out.println("WaitingThread about to exit waiting on objC 2");
}
}
static class CheckerThread extends Thread {
public CheckerThread() {
super("CheckerThread");
}
public void run() {
synchronized (ready) {
// wait until WaitingThread about to wait for objC
thrsync.waitForSignal();
int retryCount = 0;
while (waiter.getState() != Thread.State.WAITING
&& retryCount++ < 500) {
goSleep(100);
}
checkBlockedObject(waiter, objC, null, Thread.State.WAITING);
synchronized (objC) {
objC.notify();
}
// wait for waiter thread to about to enter
// synchronized object ready.
thrsync.waitForSignal();
// give chance for waiter thread to get blocked on
// object ready.
goSleep(50);
checkBlockedObject(waiter, ready, this, Thread.State.BLOCKED);
}
// wait for signal from waiting thread that it is about
// wait for objC.
thrsync.waitForSignal();
synchronized(objC) {
checkBlockedObject(waiter, objC, Thread.currentThread(), Thread.State.WAITING);
objC.notify();
}
}
}
public static void main(String args[]) throws Exception {
Thread mainThread = Thread.currentThread();
// Test uncontested case
LockAThread t1;
LockBThread t2;
synchronized(objC) {
// The state could be anything. The expected state value
// passed with this method is not verified.
checkBlockedObject(mainThread, null, null, Thread.State.RUNNABLE);
// Test deadlock case
// t1 holds lockA and attempts to lock B
// t2 holds lockB and attempts to lock C
t1 = new LockAThread();
t1.start();
t2 = new LockBThread();
t2.start();
t2.aboutToLockC();
checkBlockedObject(t1, objB, t2, Thread.State.BLOCKED);
checkBlockedObject(t2, objC, mainThread, Thread.State.BLOCKED);
long[] expectedThreads = new long[3];
expectedThreads[0] = t1.getId(); // blocked on lockB
expectedThreads[1] = t2.getId(); // owner of lockB blocking on lockC
expectedThreads[2] = mainThread.getId(); // owner of lockC
findThreadsBlockedOn(objB, expectedThreads);
}
goSleep(100);
// Test Object.wait() case
waiter = new WaitingThread();
waiter.start();
checker = new CheckerThread();
checker.start();
try {
waiter.join();
checker.join();
} catch (InterruptedException e) {
e.printStackTrace();
testFailed = true;
}
if (testFailed) {
throw new RuntimeException("TEST FAILED.");
}
System.out.println("Test passed.");
}
private static ThreadInfo findOwnerInfo(ThreadInfo[] infos, String lock)
throws Exception {
ThreadInfo ownerInfo = null;
for (int i = 0; i < infos.length; i++) {
String blockedLock = infos[i].getLockName();
if (lock.equals(blockedLock)) {
long threadId = infos[i].getLockOwnerId();
if (threadId == -1) {
throw new RuntimeException("TEST FAILED: " +
lock + " expected to have owner");
}
for (int j = 0; j < infos.length; j++) {
if (infos[j].getThreadId() == threadId) {
ownerInfo = infos[j];
break;
}
}
}
}
return ownerInfo;
}
private static void findThreadsBlockedOn(Object o, long[] expectedThreads)
throws Exception {
String lock = getLockName(o);
// Check with ThreadInfo with no stack trace (i.e. no safepoint)
ThreadInfo[] infos = tm.getThreadInfo(tm.getAllThreadIds());
doCheck(infos, lock, expectedThreads);
// Check with ThreadInfo with stack trace
infos = tm.getThreadInfo(tm.getAllThreadIds(), 1);
doCheck(infos, lock, expectedThreads);
}
private static void doCheck(ThreadInfo[] infos, String lock, long[] expectedThreads)
throws Exception {
ThreadInfo ownerInfo = null;
// Find the thread who is blocking on lock
for (int i = 0; i < infos.length; i++) {
String blockedLock = infos[i].getLockName();
if (lock.equals(blockedLock)) {
System.out.print(infos[i].getThreadName() +
" blocked on " + blockedLock);
ownerInfo = infos[i];
}
}
long[] threads = new long[10];
int count = 0;
threads[count++] = ownerInfo.getThreadId();
while (ownerInfo != null && ownerInfo.getThreadState() == Thread.State.BLOCKED) {
ownerInfo = findOwnerInfo(infos, lock);
threads[count++] = ownerInfo.getThreadId();
System.out.println(" Owner = " + ownerInfo.getThreadName() +
" id = " + ownerInfo.getThreadId());
lock = ownerInfo.getLockName();
System.out.print(ownerInfo.getThreadName() + " Id = " +
ownerInfo.getThreadId() +
" blocked on " + lock);
}
System.out.println();
if (count != expectedThreads.length) {
throw new RuntimeException("TEST FAILED: " +
"Expected chain of threads not matched; current count =" + count);
}
for (int i = 0; i < count; i++) {
if (threads[i] != expectedThreads[i]) {
System.out.println("TEST FAILED: " +
"Unexpected thread in the chain " + threads[i] +
" expected to be " + expectedThreads[i]);
}
}
}
}