blob: fa7a5b63a6fb7aef4b44dcc0e8c6518ec3c650e3 [file] [log] [blame]
/*
* Copyright (c) 2003, 2016, 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
* @author Jaroslav Bachorik
*
* @library /lib/testlibrary
*
* @build jdk.testlibrary.*
* @run main/othervm Locks
*/
import java.lang.management.*;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.Phaser;
import java.util.function.Predicate;
import jdk.testlibrary.LockFreeLogManager;
public class Locks {
private static final Object OBJA = new Object();
private static final Object OBJB = new Object();
private static final EnhancedWaiter OBJC = new EnhancedWaiter();
private static final ThreadMXBean TM = ManagementFactory.getThreadMXBean();
private static final LockFreeLogManager LOGGER = new LockFreeLogManager();
private static String getLockName(Object lock) {
if (lock == null) return null;
return lock.getClass().getName() + '@' +
Integer.toHexString(System.identityHashCode(lock));
}
private static void assertNoLock(Thread t) {
if (t == null) {
return;
}
Optional<ThreadInfo> result = Arrays.asList(
TM.getThreadInfo(TM.getAllThreadIds(), true, true)).
stream().
filter(tInfo -> (tInfo != null && tInfo.getLockOwnerName() != null)
? tInfo.getLockOwnerName().equals(t.getName()) : false).
findAny();
if (result.isPresent()) {
throw new RuntimeException("Thread " + t.getName() + " is not "
+ "supposed to be hold any lock. Currently owning lock : "
+ result.get().getLockName());
}
}
/*
* Handy debug function to check if error condition is because of test code or not.
*/
private static void printStackTrace(Thread thread) {
if (thread == null) {
return;
}
StackTraceElement[] stackTrace = thread.getStackTrace();
log("Stack dump : Thread -> " + thread.getName());
for (StackTraceElement stackTraceEl : stackTrace) {
log("\t" + stackTraceEl.toString());
}
}
private static void assertThreadState(Thread t, Thread.State expectedState) {
long tid = t.getId();
if (expectedState == Thread.State.BLOCKED
&& TM.getThreadInfo(tid).getThreadState() != Thread.State.BLOCKED) {
int retryCount = 0;
printStackTrace(t);
while (TM.getThreadInfo(tid).getThreadState() != Thread.State.BLOCKED) {
if (retryCount++ > 500) {
printStackTrace(t);
throw new RuntimeException("Thread " + t.getName() + " is at "
+ TM.getThreadInfo(tid).getThreadState() + " state but is expected to "
+ "be in Thread.State = " + expectedState);
}
goSleep(100);
}
}
if (!TM.getThreadInfo(tid).getThreadState().equals(expectedState)) {
printStackTrace(t);
throw new RuntimeException("Thread " + t.getName() + " is at "
+ TM.getThreadInfo(tid).getThreadState() + " state but is expected to "
+ "be in Thread.State = " + expectedState);
}
}
/*
* Do slow check if thread is blocked on a lock. It is possible that last thread
* to come out of Phaser might still be in Phaser call stack (Unsafe.park) and
* hence might eventually acquire expected lock.
*/
private static void checkBlockedObject(Thread t, Object lock, Thread owner) {
long tid = t.getId();
String result = TM.getThreadInfo(tid).getLockName();
final String expectedLock = (lock != null ? getLockName(lock) : null);
Predicate<String> p = (res) -> ((res != null && !res.equals(expectedLock))
|| (res == null && expectedLock != null));
if (p.test(result)) {
printStackTrace(t);
int retryCount = 0;
while (p.test(result)) {
if (retryCount++ > 500) {
printStackTrace(t);
throw new RuntimeException("Thread " + t.getName() + " is blocked on "
+ expectedLock + " but got " + result);
}
goSleep(100);
result = TM.getThreadInfo(tid).getLockName();
}
}
result = TM.getThreadInfo(tid).getLockOwnerName();
final String expectedOwner = (owner != null ? owner.getName() : null);
p = (res) -> ((res != null && !res.equals(expectedOwner))
|| (res == null && expectedOwner != null));
if (p.test(result)) {
printStackTrace(t);
throw new RuntimeException("Owner of " + lock + " should be "
+ expectedOwner + " but got " + result);
}
}
private static void goSleep(long ms){
try {
Thread.sleep(ms);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
private static volatile int dummyCounter = 0;
static class LockAThread extends Thread {
private final Phaser p;
public LockAThread(Phaser p) {
super("LockAThread");
this.p = p;
}
@Override
public void run() {
synchronized(OBJA) {
// block here while LockBThread holds OBJB
log("LockAThread about to block on OBJB");
p.arriveAndAwaitAdvance(); // Phase 1 (blocking)
synchronized(OBJB) {
dummyCounter++;
}
}
p.arriveAndAwaitAdvance(); // Phase 2 (blocking)
log("LockAThread about to exit");
// Make sure the current thread is not holding any lock
assertNoLock(this);
}
}
static class LockBThread extends Thread {
private final Phaser p;
public LockBThread(Phaser p) {
super("LockBThread");
this.p = p;
}
@Override
public void run() {
synchronized(OBJB) {
log("LockBThread about to block on OBJC");
p.arriveAndAwaitAdvance(); // Phase 1 (blocking)
// Signal main thread about to block on OBJC
synchronized(OBJC) {
dummyCounter++;
}
}
p.arriveAndAwaitAdvance(); // Phase 2 (blocking)
log("LockBThread about to exit");
// Make sure the current thread is not holding any lock
assertNoLock(this);
}
}
/*
* Must be invoked from within a synchronized context
*/
private static class EnhancedWaiter {
boolean isNotified = false;
public void doWait() throws InterruptedException {
while (!isNotified) {
wait();
}
isNotified = false;
}
public void doNotify() {
isNotified = true;
notify();
}
}
private static WaitingThread waiter;
private static final Object ready = new Object();
private static CheckerThread checker;
static class WaitingThread extends Thread {
private final Phaser p;
volatile boolean waiting = false;
public WaitingThread(Phaser p) {
super("WaitingThread");
this.p = p;
}
@Override
public void run() {
synchronized(OBJC) {
log("WaitingThread about to wait on OBJC");
try {
// Signal checker thread, about to wait on OBJC.
waiting = false;
p.arriveAndAwaitAdvance(); // Phase 1 (waiting)
waiting = true;
OBJC.doWait();
} catch (InterruptedException e) {
throw new RuntimeException(e); // Do not continue test
}
// block until CheckerThread finishes checking
log("WaitingThread about to block on ready");
// signal checker thread that it is about acquire
// object ready.
p.arriveAndAwaitAdvance(); // Phase 2 (waiting)
synchronized(ready) {
dummyCounter++;
}
}
synchronized(OBJC) {
try {
// signal checker thread, about to wait on OBJC
waiting = false;
p.arriveAndAwaitAdvance(); // Phase 3 (waiting)
waiting = true;
OBJC.doWait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log("WaitingThread about to exit waiting on OBJC 2");
}
public void waitForWaiting() {
p.arriveAndAwaitAdvance();
while (!waiting) {
goSleep(10);
}
waitForState(State.WAITING);
}
public void waitForBlocked() {
p.arriveAndAwaitAdvance();
waitForState(State.BLOCKED);
}
private void waitForState(Thread.State state) {
while (!waiter.isInterrupted() && waiter.getState() != state) {
Thread.yield();
}
}
}
static class CheckerThread extends Thread {
public CheckerThread() {
super("CheckerThread");
}
@Override
public void run() {
synchronized(ready) {
// wait until WaitingThread about to wait for OBJC
waiter.waitForWaiting(); // Phase 1 (waiting)
assertThreadState(waiter, Thread.State.WAITING);
checkBlockedObject(waiter, OBJC, null);
synchronized(OBJC) {
OBJC.doNotify();
}
// wait for waiter thread to about to enter
// synchronized object ready.
waiter.waitForBlocked(); // Phase 2 (waiting)
assertThreadState(waiter, Thread.State.BLOCKED);
checkBlockedObject(waiter, ready, this);
}
// wait for signal from waiting thread that it is about
// wait for OBJC.
waiter.waitForWaiting(); // Phase 3 (waiting)
synchronized(OBJC) {
assertThreadState(waiter, Thread.State.WAITING);
checkBlockedObject(waiter, OBJC, Thread.currentThread());
OBJC.doNotify();
}
}
}
public static void main(String args[]) throws Exception {
try {
Thread mainThread = Thread.currentThread();
// Test uncontested case
LockAThread t1;
LockBThread t2;
Phaser p = new Phaser(3);
synchronized(OBJC) {
// Make sure the main thread is not holding any lock
assertNoLock(mainThread);
// Test deadlock case
// t1 holds lockA and attempts to lock B
// t2 holds lockB and attempts to lock C
t1 = new LockAThread(p);
t1.start();
t2 = new LockBThread(p);
t2.start();
p.arriveAndAwaitAdvance(); // Phase 1 (blocking)
assertThreadState(t2, Thread.State.BLOCKED);
checkBlockedObject(t2, OBJC, mainThread);
assertThreadState(t1, Thread.State.BLOCKED);
checkBlockedObject(t1, OBJB, t2);
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);
}
p.arriveAndAwaitAdvance(); // Phase 2 (blocking)
p = new Phaser(2);
// Test Object.wait() case
waiter = new WaitingThread(p);
waiter.start();
checker = new CheckerThread();
checker.start();
try {
waiter.join();
checker.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} finally { // log all the messages to STDOUT
System.out.println(LOGGER.toString());
}
System.out.println("Test passed.");
}
private static ThreadInfo findOwnerInfo(ThreadInfo[] infos, String lock)
throws Exception {
ThreadInfo ownerInfo = null;
for (ThreadInfo info : infos) {
String blockedLock = info.getLockName();
if (lock.equals(blockedLock)) {
long threadId = info.getLockOwnerId();
if (threadId == -1) {
throw new RuntimeException("TEST FAILED: " +
lock + " expected to have owner");
}
for (ThreadInfo info1 : infos) {
if (info1.getThreadId() == threadId) {
ownerInfo = info1;
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 (ThreadInfo info : infos) {
String blockedLock = info.getLockName();
if (lock.equals(blockedLock)) {
log("%s blocked on %s", info.getThreadName(), blockedLock);
ownerInfo = info;
}
}
if (ownerInfo == null) {
throw new RuntimeException("TEST FAILED: " +
"Can't retrieve ThreadInfo for the blocked thread");
}
long[] threads = new long[10];
int count = 0;
threads[count++] = ownerInfo.getThreadId();
while (ownerInfo.getThreadState() == Thread.State.BLOCKED) {
ownerInfo = findOwnerInfo(infos, lock);
threads[count++] = ownerInfo.getThreadId();
log(" Owner = %s id = %d",
ownerInfo.getThreadName(),
ownerInfo.getThreadId()
);
lock = ownerInfo.getLockName();
log("%s Id = %d blocked on %s",
ownerInfo.getThreadName(),
ownerInfo.getThreadId(),
lock
);
}
log("");
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]) {
log("TEST FAILED: Unexpected thread in the chain %s expected to be %s",
threads[i],
expectedThreads[i]
);
}
}
}
private static void log(String format, Object ... args) {
LOGGER.log(format + "%n", args);
}
}