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.testlibrary.Asserts;
  31 import jdk.testlibrary.dcmd.PidJcmdExecutor;
  32 import jdk.testlibrary.jfr.EventNames;
  33 import jdk.testlibrary.jfr.Events;
  34 import jdk.testlibrary.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 /lib/testlibrary
  44  *
  45  * @summary the default value of BiasedLockingStartupDelay in openjdk8 is 4000, but it has been changed to 0 in openjdk10,
  46  *          see https://bugs.openjdk.java.net/browse/JDK-8180421. BiasedLockingStartupDelay must be set to 0 here, otherwise
  47  *          this test will fail at testSingleRevocation, because no BiasedLockRevocation event will happen until biased locking
  48  *          enabled.
  49  * @run main/othervm -XX:+PrintJFRLog -XX:BiasedLockingStartupDelay=0 jdk.jfr.event.runtime.TestBiasedLockRevocationEvents
  50  */
  51 public class TestBiasedLockRevocationEvents {
  52 
  53     public static void main(String[] args) throws Throwable {
  54         testSingleRevocation();
  55         testBulkRevocation();
  56         testSelfRevocation();
  57         testExitedThreadRevocation();
  58         testBulkRevocationNoRebias();
  59         testRevocationSafepointIdCorrelation();
  60     }
  61 
  62     // Default value of BiasedLockingBulkRebiasThreshold is 20, and BiasedLockingBulkRevokeTreshold is 40.
  63     // Using a value that will hit the first threshold once, and the second one the next time.
  64     private static final int BULK_REVOKE_THRESHOLD = 25;
  65 
  66     static void touch(Object lock) {
  67         synchronized(lock) {
  68         }
  69     }
  70 
  71     static Thread triggerRevocation(int numRevokes, Class<?> lockClass) throws Throwable {
  72         Object[] locks = new Object[numRevokes];
  73         for (int i = 0; i < locks.length; ++i) {
  74             locks[i] = lockClass.getDeclaredConstructor().newInstance();
  75             touch(locks[i]);
  76         }
  77 
  78         Thread biasBreaker = new Thread("BiasBreaker") {
  79             @Override
  80             public void run() {
  81                 for (Object lock : locks) {
  82                     touch(lock);
  83                 }
  84             }
  85         };
  86 
  87         biasBreaker.start();
  88         biasBreaker.join();
  89 
  90         return biasBreaker;
  91     }
  92 
  93     // Basic stack trace validation, checking the name of the leaf method
  94     static void validateStackTrace(RecordedStackTrace stackTrace, String leafMethodName) {
  95         List<RecordedFrame> frames = stackTrace.getFrames();
  96         Asserts.assertFalse(frames.isEmpty());
  97         String name = frames.get(0).getMethod().getName();
  98         Asserts.assertEquals(name, leafMethodName);
  99     }
 100 
 101     // Validates that the given stack trace refers to lock.touch(); in triggerRevocation
 102     static void validateStackTrace(RecordedStackTrace stackTrace) {
 103         validateStackTrace(stackTrace, "touch");
 104     }
 105 
 106     static void testSingleRevocation() throws Throwable {
 107         class MyLock {};
 108 
 109         Recording recording = new Recording();
 110 
 111         recording.enable(EventNames.BiasedLockRevocation);
 112         recording.start();
 113 
 114         Thread biasBreaker = triggerRevocation(1, MyLock.class);
 115 
 116         recording.stop();
 117 
 118         List<RecordedEvent> events = Events.fromRecording(recording);
 119 
 120         // We may or may not catch a second revocation from the biasBreaker thread exiting
 121         Asserts.assertGreaterThanOrEqual(events.size(), 1);
 122 
 123         RecordedEvent event = null;
 124         for (RecordedEvent recordedEvent : events) {
 125             RecordedClass lockClass = recordedEvent.getValue("lockClass");
 126             if (lockClass.getName().equals(MyLock.class.getName())) {
 127                 event = recordedEvent;
 128                 break;
 129             }
 130         }
 131         Asserts.assertNotNull(event);
 132         Events.assertEventThread(event, biasBreaker);
 133         Events.assertEventThread(event, "previousOwner", Thread.currentThread());
 134 
 135         RecordedClass lockClass = event.getValue("lockClass");
 136         Asserts.assertEquals(lockClass.getName(), MyLock.class.getName());
 137 
 138         validateStackTrace(event.getStackTrace());
 139     }
 140 
 141     static void testBulkRevocation() throws Throwable {
 142         class MyLock {};
 143 
 144         Recording recording = new Recording();
 145 
 146         recording.enable(EventNames.BiasedLockClassRevocation);
 147         recording.start();
 148 
 149         Thread biasBreaker = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 150 
 151         recording.stop();
 152         List<RecordedEvent> events = Events.fromRecording(recording);
 153         Asserts.assertEQ(events.size(), 1);
 154 
 155         RecordedEvent event = events.get(0);
 156         Events.assertEventThread(event, biasBreaker);
 157         Events.assertField(event, "disableBiasing").equal(false);
 158 
 159         RecordedClass lockClass = event.getValue("revokedClass");
 160         Asserts.assertEquals(lockClass.getName(), MyLock.class.getName());
 161 
 162         validateStackTrace(event.getStackTrace());
 163     }
 164 
 165     static void testSelfRevocation() throws Throwable {
 166         class MyLock {};
 167 
 168         Recording recording = new Recording();
 169 
 170         recording.enable(EventNames.BiasedLockSelfRevocation);
 171         recording.start();
 172 
 173         MyLock l = new MyLock();
 174         touch(l);
 175         Thread.holdsLock(l);
 176 
 177         recording.stop();
 178 
 179         List<RecordedEvent> events = Events.fromRecording(recording);
 180         Asserts.assertEQ(events.size(), 1);
 181 
 182         RecordedEvent event = events.get(0);
 183         Events.assertEventThread(event, Thread.currentThread());
 184 
 185         validateStackTrace(event.getStackTrace(), "holdsLock");
 186     }
 187 
 188     static void testExitedThreadRevocation() throws Throwable {
 189         class MyLock {};
 190 
 191         Recording recording = new Recording();
 192 
 193         recording.enable(EventNames.BiasedLockRevocation);
 194         recording.start();
 195 
 196         FutureTask<MyLock> lockerTask = new FutureTask<>(() -> {
 197            MyLock l = new MyLock();
 198            touch(l);
 199            return l;
 200         });
 201 
 202         Thread locker = new Thread(lockerTask, "BiasLocker");
 203         locker.start();
 204         locker.join();
 205 
 206         // Even after joining, the VM has a bit more work to do before the thread is actually removed
 207         // from the threads list. Ensure that this has happened before proceeding.
 208         while (true) {
 209             PidJcmdExecutor jcmd = new PidJcmdExecutor();
 210             OutputAnalyzer oa = jcmd.execute("Thread.print", true);
 211             String lockerThreadFound = oa.firstMatch("BiasLocker");
 212             if (lockerThreadFound == null) {
 213                 break;
 214             }
 215         };
 216 
 217         MyLock l = lockerTask.get();
 218         touch(l);
 219 
 220         recording.stop();
 221         List<RecordedEvent> events = Events.fromRecording(recording);
 222         Events.hasEvents(events);
 223 
 224         // Joining the locker thread can cause revocations as well, search for the interesting one
 225         for (RecordedEvent event : events) {
 226             RecordedClass lockClass = event.getValue("lockClass");
 227             if (lockClass.getName().equals(MyLock.class.getName())) {
 228                 Events.assertEventThread(event, Thread.currentThread());
 229                 // Previous owner will usually be null, but can also be a thread that
 230                 // was created after the BiasLocker thread exited due to address reuse.
 231                 RecordedThread prevOwner = event.getValue("previousOwner");
 232                 if (prevOwner != null) {
 233                     Asserts.assertNE(prevOwner.getJavaName(), "BiasLocker");
 234                 }
 235                 validateStackTrace(event.getStackTrace());
 236                 return;
 237             }
 238         }
 239         Asserts.fail("Did not find any revocation event for MyLock");
 240     }
 241 
 242     static void testBulkRevocationNoRebias() throws Throwable {
 243         class MyLock {};
 244 
 245         Recording recording = new Recording();
 246 
 247         recording.enable(EventNames.BiasedLockClassRevocation);
 248         recording.start();
 249 
 250         Thread biasBreaker0 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 251         Thread biasBreaker1 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 252 
 253         recording.stop();
 254         List<RecordedEvent> events = Events.fromRecording(recording);
 255         Asserts.assertEQ(events.size(), 2);
 256 
 257         RecordedEvent eventRebias = events.get(0);
 258         Events.assertEventThread(eventRebias, biasBreaker0);
 259         Events.assertField(eventRebias, "disableBiasing").equal(false);
 260 
 261         RecordedEvent eventNoRebias = events.get(1);
 262         Events.assertEventThread(eventNoRebias, biasBreaker1);
 263         Events.assertField(eventNoRebias, "disableBiasing").equal(true);
 264 
 265         RecordedClass lockClassRebias = eventRebias.getValue("revokedClass");
 266         Asserts.assertEquals(lockClassRebias.getName(), MyLock.class.getName());
 267         RecordedClass lockClassNoRebias = eventNoRebias.getValue("revokedClass");
 268         Asserts.assertEquals(lockClassNoRebias.getName(), MyLock.class.getName());
 269 
 270         validateStackTrace(eventRebias.getStackTrace());
 271         validateStackTrace(eventNoRebias.getStackTrace());
 272     }
 273 
 274     static void testRevocationSafepointIdCorrelation() throws Throwable {
 275         class MyLock {};
 276 
 277         Recording recording = new Recording();
 278 
 279         recording.enable(EventNames.BiasedLockRevocation);
 280         recording.enable(EventNames.BiasedLockClassRevocation);
 281         recording.enable(EventNames.ExecuteVMOperation);
 282         recording.start();
 283 
 284         triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 285 
 286         recording.stop();
 287         List<RecordedEvent> events = Events.fromRecording(recording);
 288 
 289         // Find all biased locking related VMOperation events
 290         HashMap<Integer, RecordedEvent> vmOperations = new HashMap<Integer, RecordedEvent>();
 291         for (RecordedEvent event : events) {
 292             if ((event.getEventType().getName().equals(EventNames.ExecuteVMOperation)) &&
 293                     (event.getValue("operation").toString().contains("Bias"))) {
 294                 vmOperations.put(event.getValue("safepointId"), event);
 295             }
 296         }
 297 
 298         int revokeCount = 0;
 299         int bulkRevokeCount = 0;
 300 
 301         // Match all revoke events to a corresponding VMOperation event
 302         for (RecordedEvent event : events) {
 303             if (event.getEventType().getName().equals(EventNames.BiasedLockRevocation)) {
 304                 RecordedEvent vmOpEvent = vmOperations.remove(event.getValue("safepointId"));
 305                 if (event.getValue("safepointId").toString().equals("-1")) {
 306                     Asserts.assertEquals(vmOpEvent, null);
 307                 } else {
 308                     Events.assertField(vmOpEvent, "operation").equal("RevokeBias");
 309                     revokeCount++;
 310                 }
 311             } else if (event.getEventType().getName().equals(EventNames.BiasedLockClassRevocation)) {
 312                 RecordedEvent vmOpEvent = vmOperations.remove(event.getValue("safepointId"));
 313                 Events.assertField(vmOpEvent, "operation").equal("BulkRevokeBias");
 314                 bulkRevokeCount++;
 315             }
 316         }
 317 
 318         // All VMOperations should have had a matching revoke event
 319         Asserts.assertEQ(vmOperations.size(), 0);
 320 
 321         Asserts.assertGT(bulkRevokeCount, 0);
 322         Asserts.assertGT(revokeCount, bulkRevokeCount);
 323     }
 324 }