1 /*
   2  * Copyright (c) 2017, 2018, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.jfr.event.runtime;
  27 
  28 import jdk.jfr.Recording;
  29 import jdk.jfr.consumer.*;
  30 import jdk.test.lib.Asserts;
  31 import jdk.test.lib.dcmd.PidJcmdExecutor;
  32 import jdk.test.lib.jfr.EventNames;
  33 import jdk.test.lib.jfr.Events;
  34 import jdk.test.lib.process.OutputAnalyzer;
  35 
  36 import java.util.HashMap;
  37 import java.util.List;
  38 import java.util.concurrent.FutureTask;
  39 
  40 /*
  41  * @test
  42  * @key jfr
  43  * @library /test/lib
  44  *
  45  * @run main/othervm jdk.jfr.event.runtime.TestBiasedLockRevocationEvents
  46  */
  47 public class TestBiasedLockRevocationEvents {
  48 
  49     public static void main(String[] args) throws Throwable {
  50         testSingleRevocation();
  51         testBulkRevocation();
  52         testSelfRevocation();
  53         testExitedThreadRevocation();
  54         testBulkRevocationNoRebias();
  55         testRevocationSafepointIdCorrelation();
  56     }
  57 
  58     // Default value of BiasedLockingBulkRebiasThreshold is 20, and BiasedLockingBulkRevokeTreshold is 40.
  59     // Using a value that will hit the first threshold once, and the second one the next time.
  60     private static final int BULK_REVOKE_THRESHOLD = 25;
  61 
  62     static void touch(Object lock) {
  63         synchronized(lock) {
  64         }
  65     }
  66 
  67     static Thread triggerRevocation(int numRevokes, Class<?> lockClass) throws Throwable {
  68         Object[] locks = new Object[numRevokes];
  69         for (int i = 0; i < locks.length; ++i) {
  70             locks[i] = lockClass.getDeclaredConstructor().newInstance();
  71             touch(locks[i]);
  72         }
  73 
  74         Thread biasBreaker = new Thread("BiasBreaker") {
  75             @Override
  76             public void run() {
  77                 for (Object lock : locks) {
  78                     touch(lock);
  79                 }
  80             }
  81         };
  82 
  83         biasBreaker.start();
  84         biasBreaker.join();
  85 
  86         return biasBreaker;
  87     }
  88 
  89     // Basic stack trace validation, checking the name of the leaf method
  90     static void validateStackTrace(RecordedStackTrace stackTrace, String leafMethodName) {
  91         List<RecordedFrame> frames = stackTrace.getFrames();
  92         Asserts.assertFalse(frames.isEmpty());
  93         String name = frames.get(0).getMethod().getName();
  94         Asserts.assertEquals(name, leafMethodName);
  95     }
  96 
  97     // Validates that the given stack trace refers to lock.touch(); in triggerRevocation
  98     static void validateStackTrace(RecordedStackTrace stackTrace) {
  99         validateStackTrace(stackTrace, "touch");
 100     }
 101 
 102     static void testSingleRevocation() throws Throwable {
 103         class MyLock {};
 104 
 105         Recording recording = new Recording();
 106 
 107         recording.enable(EventNames.BiasedLockRevocation);
 108         recording.start();
 109 
 110         Thread biasBreaker = triggerRevocation(1, MyLock.class);
 111 
 112         recording.stop();
 113 
 114         List<RecordedEvent> events = Events.fromRecording(recording);
 115 
 116         // We may or may not catch a second revocation from the biasBreaker thread exiting
 117         Asserts.assertGreaterThanOrEqual(events.size(), 1);
 118 
 119         RecordedEvent event = events.get(0);
 120         Events.assertEventThread(event, biasBreaker);
 121         Events.assertEventThread(event, "previousOwner", Thread.currentThread());
 122 
 123         RecordedClass lockClass = event.getValue("lockClass");
 124         Asserts.assertEquals(lockClass.getName(), MyLock.class.getName());
 125 
 126         validateStackTrace(event.getStackTrace());
 127     }
 128 
 129     static void testBulkRevocation() throws Throwable {
 130         class MyLock {};
 131 
 132         Recording recording = new Recording();
 133 
 134         recording.enable(EventNames.BiasedLockClassRevocation);
 135         recording.start();
 136 
 137         Thread biasBreaker = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 138 
 139         recording.stop();
 140         List<RecordedEvent> events = Events.fromRecording(recording);
 141         Asserts.assertEQ(events.size(), 1);
 142 
 143         RecordedEvent event = events.get(0);
 144         Events.assertEventThread(event, biasBreaker);
 145         Events.assertField(event, "disableBiasing").equal(false);
 146 
 147         RecordedClass lockClass = event.getValue("revokedClass");
 148         Asserts.assertEquals(lockClass.getName(), MyLock.class.getName());
 149 
 150         validateStackTrace(event.getStackTrace());
 151     }
 152 
 153     static void testSelfRevocation() throws Throwable {
 154         class MyLock {};
 155 
 156         Recording recording = new Recording();
 157 
 158         recording.enable(EventNames.BiasedLockSelfRevocation);
 159         recording.start();
 160 
 161         MyLock l = new MyLock();
 162         touch(l);
 163         Thread.holdsLock(l);
 164 
 165         recording.stop();
 166 
 167         List<RecordedEvent> events = Events.fromRecording(recording);
 168         Asserts.assertEQ(events.size(), 1);
 169 
 170         RecordedEvent event = events.get(0);
 171         Events.assertEventThread(event, Thread.currentThread());
 172 
 173         validateStackTrace(event.getStackTrace(), "holdsLock");
 174     }
 175 
 176     static void testExitedThreadRevocation() throws Throwable {
 177         class MyLock {};
 178 
 179         Recording recording = new Recording();
 180 
 181         recording.enable(EventNames.BiasedLockRevocation);
 182         recording.start();
 183 
 184         FutureTask<MyLock> lockerTask = new FutureTask<>(() -> {
 185            MyLock l = new MyLock();
 186            touch(l);
 187            return l;
 188         });
 189 
 190         Thread locker = new Thread(lockerTask, "BiasLocker");
 191         locker.start();
 192         locker.join();
 193 
 194         // Even after joining, the VM has a bit more work to do before the thread is actually removed
 195         // from the threads list. Ensure that this has happened before proceeding.
 196         while (true) {
 197             PidJcmdExecutor jcmd = new PidJcmdExecutor();
 198             OutputAnalyzer oa = jcmd.execute("Thread.print", true);
 199             String lockerThreadFound = oa.firstMatch("BiasLocker");
 200             if (lockerThreadFound == null) {
 201                 break;
 202             }
 203         };
 204 
 205         MyLock l = lockerTask.get();
 206         touch(l);
 207 
 208         recording.stop();
 209         List<RecordedEvent> events = Events.fromRecording(recording);
 210         Events.hasEvents(events);
 211 
 212         // Joining the locker thread can cause revocations as well, search for the interesting one
 213         for (RecordedEvent event : events) {
 214             RecordedClass lockClass = event.getValue("lockClass");
 215             if (lockClass.getName().equals(MyLock.class.getName())) {
 216                 Events.assertEventThread(event, Thread.currentThread());
 217                 // Previous owner will usually be null, but can also be a thread that
 218                 // was created after the BiasLocker thread exited due to address reuse.
 219                 RecordedThread prevOwner = event.getValue("previousOwner");
 220                 if (prevOwner != null) {
 221                     Asserts.assertNE(prevOwner.getJavaName(), "BiasLocker");
 222                 }
 223                 validateStackTrace(event.getStackTrace());
 224                 return;
 225             }
 226         }
 227         Asserts.fail("Did not find any revocation event for MyLock");
 228     }
 229 
 230     static void testBulkRevocationNoRebias() throws Throwable {
 231         class MyLock {};
 232 
 233         Recording recording = new Recording();
 234 
 235         recording.enable(EventNames.BiasedLockClassRevocation);
 236         recording.start();
 237 
 238         Thread biasBreaker0 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 239         Thread biasBreaker1 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 240 
 241         recording.stop();
 242         List<RecordedEvent> events = Events.fromRecording(recording);
 243         Asserts.assertEQ(events.size(), 2);
 244 
 245         RecordedEvent eventRebias = events.get(0);
 246         Events.assertEventThread(eventRebias, biasBreaker0);
 247         Events.assertField(eventRebias, "disableBiasing").equal(false);
 248 
 249         RecordedEvent eventNoRebias = events.get(1);
 250         Events.assertEventThread(eventNoRebias, biasBreaker1);
 251         Events.assertField(eventNoRebias, "disableBiasing").equal(true);
 252 
 253         RecordedClass lockClassRebias = eventRebias.getValue("revokedClass");
 254         Asserts.assertEquals(lockClassRebias.getName(), MyLock.class.getName());
 255         RecordedClass lockClassNoRebias = eventNoRebias.getValue("revokedClass");
 256         Asserts.assertEquals(lockClassNoRebias.getName(), MyLock.class.getName());
 257 
 258         validateStackTrace(eventRebias.getStackTrace());
 259         validateStackTrace(eventNoRebias.getStackTrace());
 260     }
 261 
 262     static void testRevocationSafepointIdCorrelation() throws Throwable {
 263         class MyLock {};
 264 
 265         Recording recording = new Recording();
 266 
 267         recording.enable(EventNames.BiasedLockRevocation);
 268         recording.enable(EventNames.BiasedLockClassRevocation);
 269         recording.enable(EventNames.ExecuteVMOperation);
 270         recording.start();
 271 
 272         triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 273 
 274         recording.stop();
 275         List<RecordedEvent> events = Events.fromRecording(recording);
 276 
 277         // Find all biased locking related VMOperation events
 278         HashMap<Integer, RecordedEvent> vmOperations = new HashMap<Integer, RecordedEvent>();
 279         for (RecordedEvent event : events) {
 280             if ((event.getEventType().getName().equals(EventNames.ExecuteVMOperation)) &&
 281                     (event.getValue("operation").toString().contains("Bias"))) {
 282                 vmOperations.put(event.getValue("safepointId"), event);
 283             }
 284         }
 285 
 286         int revokeCount = 0;
 287         int bulkRevokeCount = 0;
 288 
 289         // Match all revoke events to a corresponding VMOperation event
 290         for (RecordedEvent event : events) {
 291             if (event.getEventType().getName().equals(EventNames.BiasedLockRevocation)) {
 292                 RecordedEvent vmOpEvent = vmOperations.remove(event.getValue("safepointId"));
 293                 if (event.getValue("safepointId").toString().equals("-1")) {
 294                     Asserts.assertEquals(vmOpEvent, null);
 295                 } else {
 296                     Events.assertField(vmOpEvent, "operation").equal("RevokeBias");
 297                     revokeCount++;
 298                 }
 299             } else if (event.getEventType().getName().equals(EventNames.BiasedLockClassRevocation)) {
 300                 RecordedEvent vmOpEvent = vmOperations.remove(event.getValue("safepointId"));
 301                 Events.assertField(vmOpEvent, "operation").equal("BulkRevokeBias");
 302                 bulkRevokeCount++;
 303             }
 304         }
 305 
 306         // All VMOperations should have had a matching revoke event
 307         Asserts.assertEQ(vmOperations.size(), 0);
 308 
 309         Asserts.assertGT(bulkRevokeCount, 0);
 310         Asserts.assertGT(revokeCount, bulkRevokeCount);
 311     }
 312 }