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