1 /*
   2  * Copyright (c) 2003, 2016, 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 
  24 /*
  25  * @test
  26  * @bug     4530538
  27  * @summary Basic unit test of ThreadInfo.getLockName()
  28  *          and ThreadInfo.getLockOwnerName()
  29  * @author  Mandy Chung
  30  * @author  Jaroslav Bachorik
  31  *
  32  * @library /lib/testlibrary
  33  *
  34  * @build jdk.testlibrary.*
  35  * @run main/othervm Locks
  36  */
  37 import java.lang.management.*;
  38 import java.util.Arrays;
  39 import java.util.Optional;
  40 import java.util.concurrent.Phaser;
  41 import java.util.function.Predicate;
  42 import jdk.testlibrary.LockFreeLogManager;
  43 
  44 public class Locks {
  45 
  46     private static final Object OBJA = new Object();
  47     private static final Object OBJB = new Object();
  48     private static final EnhancedWaiter OBJC = new EnhancedWaiter();
  49     private static final ThreadMXBean TM = ManagementFactory.getThreadMXBean();
  50     private static final LockFreeLogManager LOGGER = new LockFreeLogManager();
  51 
  52     private static String getLockName(Object lock) {
  53         if (lock == null) return null;
  54 
  55         return lock.getClass().getName() + '@' +
  56                 Integer.toHexString(System.identityHashCode(lock));
  57     }
  58 
  59     private static void assertNoLock(Thread t) {
  60         if (t == null) {
  61             return;
  62         }
  63         Optional<ThreadInfo> result = Arrays.asList(
  64                 TM.getThreadInfo(TM.getAllThreadIds(), true, true)).
  65                 stream().
  66                 filter(tInfo -> (tInfo != null && tInfo.getLockOwnerName() != null)
  67                         ? tInfo.getLockOwnerName().equals(t.getName()) : false).
  68                 findAny();
  69         if (result.isPresent()) {
  70             throw new RuntimeException("Thread " + t.getName() + " is not "
  71                     + "supposed to be hold any lock. Currently owning lock : "
  72                     + result.get().getLockName());
  73         }
  74     }
  75 
  76    /*
  77     * Handy debug function to check if error condition is because of test code or not.
  78     */
  79     private static void printStackTrace(Thread thread) {
  80         if (thread == null) {
  81             return;
  82         }
  83         StackTraceElement[] stackTrace = thread.getStackTrace();
  84         log("Stack dump : Thread -> " + thread.getName());
  85         for (StackTraceElement stackTraceEl : stackTrace) {
  86             log("\t" + stackTraceEl.toString());
  87         }
  88     }
  89 
  90     private static void assertThreadState(Thread t, Thread.State expectedState) {
  91         long tid = t.getId();
  92         if (expectedState == Thread.State.BLOCKED
  93                 && TM.getThreadInfo(tid).getThreadState() != Thread.State.BLOCKED) {
  94             int retryCount = 0;
  95             printStackTrace(t);
  96             while (TM.getThreadInfo(tid).getThreadState() != Thread.State.BLOCKED) {
  97                 if (retryCount++ > 500) {
  98                     printStackTrace(t);
  99                     throw new RuntimeException("Thread " + t.getName() + " is at "
 100                             + TM.getThreadInfo(tid).getThreadState() + " state but is expected to "
 101                             + "be in Thread.State = " + expectedState);
 102                 }
 103                 goSleep(100);
 104             }
 105         }
 106         if (!TM.getThreadInfo(tid).getThreadState().equals(expectedState)) {
 107             printStackTrace(t);
 108             throw new RuntimeException("Thread " + t.getName() + " is at "
 109                     + TM.getThreadInfo(tid).getThreadState() + " state but is expected to "
 110                     + "be in Thread.State = " + expectedState);
 111         }
 112     }
 113 
 114    /*
 115     * Do slow check if thread is blocked on a lock. It is possible that last thread
 116     * to come out of Phaser might still be in Phaser call stack (Unsafe.park) and
 117     * hence might eventually acquire expected lock.
 118     */
 119     private static void checkBlockedObject(Thread t, Object lock, Thread owner) {
 120         long tid = t.getId();
 121         String result = TM.getThreadInfo(tid).getLockName();
 122         final String expectedLock = (lock != null ? getLockName(lock) : null);
 123         Predicate<String> p = (res) -> ((res != null && !res.equals(expectedLock))
 124                 || (res == null && expectedLock != null));
 125 
 126         if (p.test(result)) {
 127             printStackTrace(t);
 128             int retryCount = 0;
 129             while (p.test(result)) {
 130                 if (retryCount++ > 500) {
 131                     printStackTrace(t);
 132                     throw new RuntimeException("Thread " + t.getName() + " is blocked on "
 133                             + expectedLock + " but got " + result);
 134                 }
 135                 goSleep(100);
 136                 result = TM.getThreadInfo(tid).getLockName();
 137             }
 138         }
 139 
 140         result = TM.getThreadInfo(tid).getLockOwnerName();
 141         final String expectedOwner = (owner != null ? owner.getName() : null);
 142 
 143         p = (res) -> ((res != null && !res.equals(expectedOwner))
 144                 || (res == null && expectedOwner != null));
 145         if (p.test(result)) {
 146             printStackTrace(t);
 147             throw new RuntimeException("Owner of " + lock + " should be "
 148                     + expectedOwner + " but got " + result);
 149         }
 150     }
 151 
 152     private static void goSleep(long ms){
 153         try {
 154             Thread.sleep(ms);
 155         } catch (InterruptedException ex) {
 156             throw new RuntimeException(ex);
 157         }
 158     }
 159 
 160     private static volatile int dummyCounter = 0;
 161 
 162     static class LockAThread extends Thread {
 163         private final Phaser p;
 164         public LockAThread(Phaser p) {
 165             super("LockAThread");
 166             this.p = p;
 167         }
 168         @Override
 169         public void run() {
 170             synchronized(OBJA) {
 171                 // block here while LockBThread holds OBJB
 172                 log("LockAThread about to block on OBJB");
 173                 p.arriveAndAwaitAdvance(); // Phase 1 (blocking)
 174                 synchronized(OBJB) {
 175                     dummyCounter++;
 176                 }
 177             }
 178             p.arriveAndAwaitAdvance(); // Phase 2 (blocking)
 179             log("LockAThread about to exit");
 180             // Make sure the current thread is not holding any lock
 181             assertNoLock(this);
 182         }
 183     }
 184 
 185     static class LockBThread extends Thread {
 186         private final Phaser p;
 187         public LockBThread(Phaser p) {
 188             super("LockBThread");
 189             this.p = p;
 190         }
 191         @Override
 192         public void run() {
 193             synchronized(OBJB) {
 194                 log("LockBThread about to block on OBJC");
 195                 p.arriveAndAwaitAdvance(); // Phase 1 (blocking)
 196                 // Signal main thread about to block on OBJC
 197                 synchronized(OBJC) {
 198                     dummyCounter++;
 199                 }
 200             }
 201             p.arriveAndAwaitAdvance(); // Phase 2 (blocking)
 202             log("LockBThread about to exit");
 203             // Make sure the current thread is not holding any lock
 204             assertNoLock(this);
 205         }
 206     }
 207 
 208    /*
 209     * Must be invoked from within a synchronized context
 210     */
 211     private static class EnhancedWaiter {
 212 
 213         boolean isNotified = false;
 214 
 215         public void doWait() throws InterruptedException {
 216             while (!isNotified) {
 217                 wait();
 218             }
 219             isNotified = false;
 220         }
 221 
 222         public void doNotify() {
 223             isNotified = true;
 224             notify();
 225         }
 226     }
 227 
 228     private static WaitingThread waiter;
 229     private static final Object ready = new Object();
 230     private static CheckerThread checker;
 231 
 232     static class WaitingThread extends Thread {
 233         private final Phaser p;
 234 
 235         volatile boolean waiting = false;
 236 
 237         public WaitingThread(Phaser p) {
 238             super("WaitingThread");
 239             this.p = p;
 240         }
 241         @Override
 242         public void run() {
 243             synchronized(OBJC) {
 244                 log("WaitingThread about to wait on OBJC");
 245                 try {
 246                     // Signal checker thread, about to wait on OBJC.
 247                     waiting = false;
 248                     p.arriveAndAwaitAdvance(); // Phase 1 (waiting)
 249                     waiting = true;
 250                     OBJC.doWait();
 251                 } catch (InterruptedException e) {
 252                     throw new RuntimeException(e); // Do not continue test
 253                 }
 254 
 255                 // block until CheckerThread finishes checking
 256                 log("WaitingThread about to block on ready");
 257                 // signal checker thread that it is about acquire
 258                 // object ready.
 259                 p.arriveAndAwaitAdvance(); // Phase 2 (waiting)
 260                 synchronized(ready) {
 261                     dummyCounter++;
 262                 }
 263             }
 264             synchronized(OBJC) {
 265                 try {
 266                     // signal checker thread, about to wait on OBJC
 267                     waiting = false;
 268                     p.arriveAndAwaitAdvance(); // Phase 3 (waiting)
 269                     waiting = true;
 270                     OBJC.doWait();
 271                 } catch (InterruptedException e) {
 272                     throw new RuntimeException(e);
 273                 }
 274             }
 275             log("WaitingThread about to exit waiting on OBJC 2");
 276         }
 277 
 278         public void waitForWaiting() {
 279             p.arriveAndAwaitAdvance();
 280             while (!waiting) {
 281                 goSleep(10);
 282             }
 283             waitForState(State.WAITING);
 284         }
 285 
 286         public void waitForBlocked() {
 287             p.arriveAndAwaitAdvance();
 288             waitForState(State.BLOCKED);
 289         }
 290 
 291         private void waitForState(Thread.State state) {
 292             while (!waiter.isInterrupted() && waiter.getState() != state) {
 293                 Thread.yield();
 294             }
 295         }
 296     }
 297     static class CheckerThread extends Thread {
 298         public CheckerThread() {
 299             super("CheckerThread");
 300         }
 301 
 302         @Override
 303         public void run() {
 304             synchronized(ready) {
 305                 // wait until WaitingThread about to wait for OBJC
 306                 waiter.waitForWaiting(); // Phase 1 (waiting)
 307                 assertThreadState(waiter, Thread.State.WAITING);
 308                 checkBlockedObject(waiter, OBJC, null);
 309 
 310                 synchronized(OBJC) {
 311                     OBJC.doNotify();
 312                 }
 313                 // wait for waiter thread to about to enter
 314                 // synchronized object ready.
 315                 waiter.waitForBlocked(); // Phase 2 (waiting)
 316                 assertThreadState(waiter, Thread.State.BLOCKED);
 317                 checkBlockedObject(waiter, ready, this);
 318             }
 319 
 320             // wait for signal from waiting thread that it is about
 321             // wait for OBJC.
 322             waiter.waitForWaiting(); // Phase 3 (waiting)
 323             synchronized(OBJC) {
 324                 assertThreadState(waiter, Thread.State.WAITING);
 325                 checkBlockedObject(waiter, OBJC, Thread.currentThread());
 326                 OBJC.doNotify();
 327             }
 328         }
 329     }
 330 
 331     public static void main(String args[]) throws Exception {
 332         try {
 333             Thread mainThread = Thread.currentThread();
 334 
 335             // Test uncontested case
 336             LockAThread t1;
 337             LockBThread t2;
 338 
 339             Phaser p = new Phaser(3);
 340             synchronized(OBJC) {
 341                 // Make sure the main thread is not holding any lock
 342                 assertNoLock(mainThread);
 343 
 344                 // Test deadlock case
 345                 // t1 holds lockA and attempts to lock B
 346                 // t2 holds lockB and attempts to lock C
 347                 t1 = new LockAThread(p);
 348                 t1.start();
 349 
 350                 t2 = new LockBThread(p);
 351                 t2.start();
 352 
 353                 p.arriveAndAwaitAdvance(); // Phase 1 (blocking)
 354                 assertThreadState(t2, Thread.State.BLOCKED);
 355                 checkBlockedObject(t2, OBJC, mainThread);
 356                 assertThreadState(t1, Thread.State.BLOCKED);
 357                 checkBlockedObject(t1, OBJB, t2);
 358 
 359                 long[] expectedThreads = new long[3];
 360                 expectedThreads[0] = t1.getId(); // blocked on lockB
 361                 expectedThreads[1] = t2.getId(); // owner of lockB blocking on lockC
 362                 expectedThreads[2] = mainThread.getId(); // owner of lockC
 363                 findThreadsBlockedOn(OBJB, expectedThreads);
 364             }
 365             p.arriveAndAwaitAdvance(); // Phase 2 (blocking)
 366 
 367             p = new Phaser(2);
 368             // Test Object.wait() case
 369             waiter = new WaitingThread(p);
 370             waiter.start();
 371 
 372             checker = new CheckerThread();
 373             checker.start();
 374 
 375             try {
 376                 waiter.join();
 377                 checker.join();
 378             } catch (InterruptedException e) {
 379                 throw new RuntimeException(e);
 380             }
 381         } finally { // log all the messages to STDOUT
 382             System.out.println(LOGGER.toString());
 383         }
 384         System.out.println("Test passed.");
 385     }
 386 
 387     private static ThreadInfo findOwnerInfo(ThreadInfo[] infos, String lock)
 388             throws Exception {
 389         ThreadInfo ownerInfo = null;
 390         for (ThreadInfo info : infos) {
 391             String blockedLock = info.getLockName();
 392             if (lock.equals(blockedLock)) {
 393                 long threadId = info.getLockOwnerId();
 394                 if (threadId == -1) {
 395                     throw new RuntimeException("TEST FAILED: " +
 396                             lock + " expected to have owner");
 397                 }
 398                 for (ThreadInfo info1 : infos) {
 399                     if (info1.getThreadId() == threadId) {
 400                         ownerInfo = info1;
 401                         break;
 402                     }
 403                 }
 404             }
 405         }
 406         return ownerInfo;
 407     }
 408     private static void findThreadsBlockedOn(Object o, long[] expectedThreads)
 409             throws Exception {
 410         String lock = getLockName(o);
 411         // Check with ThreadInfo with no stack trace (i.e. no safepoint)
 412         ThreadInfo[] infos = TM.getThreadInfo(TM.getAllThreadIds());
 413         doCheck(infos, lock, expectedThreads);
 414 
 415         // Check with ThreadInfo with stack trace
 416         infos = TM.getThreadInfo(TM.getAllThreadIds(), 1);
 417         doCheck(infos, lock, expectedThreads);
 418     }
 419 
 420     private static void doCheck(ThreadInfo[] infos, String lock, long[] expectedThreads)
 421             throws Exception {
 422         ThreadInfo ownerInfo = null;
 423         // Find the thread who is blocking on lock
 424         for (ThreadInfo info : infos) {
 425             String blockedLock = info.getLockName();
 426             if (lock.equals(blockedLock)) {
 427                 log("%s blocked on %s", info.getThreadName(), blockedLock);
 428                 ownerInfo = info;
 429             }
 430         }
 431         if (ownerInfo == null) {
 432             throw new RuntimeException("TEST FAILED: " +
 433                     "Can't retrieve ThreadInfo for the blocked thread");
 434         }
 435 
 436         long[] threads = new long[10];
 437         int count = 0;
 438         threads[count++] = ownerInfo.getThreadId();
 439         while (ownerInfo.getThreadState() == Thread.State.BLOCKED) {
 440             ownerInfo = findOwnerInfo(infos, lock);
 441             threads[count++] = ownerInfo.getThreadId();
 442             log(" Owner = %s  id = %d",
 443                     ownerInfo.getThreadName(),
 444                     ownerInfo.getThreadId()
 445             );
 446             lock = ownerInfo.getLockName();
 447             log("%s Id = %d  blocked on %s",
 448                     ownerInfo.getThreadName(),
 449                     ownerInfo.getThreadId(),
 450                     lock
 451             );
 452         }
 453         log("");
 454 
 455         if (count != expectedThreads.length) {
 456             throw new RuntimeException("TEST FAILED: " +
 457                     "Expected chain of threads not matched; current count =" + count);
 458         }
 459         for (int i = 0; i < count; i++) {
 460             if (threads[i] != expectedThreads[i]) {
 461                 log("TEST FAILED: Unexpected thread in the chain %s expected to be %s",
 462                     threads[i],
 463                     expectedThreads[i]
 464                 );
 465             }
 466         }
 467     }
 468 
 469     private static void log(String format, Object ... args) {
 470         LOGGER.log(format + "%n", args);
 471     }
 472 }