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.*;
  37 import java.util.concurrent.FutureTask;
  38 import java.util.stream.Collectors;
  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     // Retrieve all biased lock revocation events related to the provided lock class, sorted by start time
 103     static List<RecordedEvent> getRevocationEvents(Recording recording, String fieldName, Class<?> lockClass) throws Throwable {
 104         return Events.fromRecording(recording).stream()
 105                 .filter(e -> ((RecordedClass)e.getValue(fieldName)).getName().equals(lockClass.getName()))
 106                 .sorted(Comparator.comparing(RecordedEvent::getStartTime))
 107                 .collect(Collectors.toList());
 108     }
 109 
 110     static void testSingleRevocation() throws Throwable {
 111         class MyLock {};
 112 
 113         Recording recording = new Recording();
 114 
 115         recording.enable(EventNames.BiasedLockRevocation);
 116         recording.start();
 117 
 118         Thread biasBreaker = triggerRevocation(1, MyLock.class);
 119 
 120         recording.stop();
 121         List<RecordedEvent> events = getRevocationEvents(recording, "lockClass", MyLock.class);
 122         Asserts.assertEQ(events.size(), 1);
 123 
 124         RecordedEvent event = events.get(0);
 125         Events.assertEventThread(event, biasBreaker);
 126         Events.assertEventThread(event, "previousOwner", Thread.currentThread());
 127 
 128         RecordedClass lockClass = event.getValue("lockClass");
 129         Asserts.assertEquals(lockClass.getName(), MyLock.class.getName());
 130 
 131         validateStackTrace(event.getStackTrace());
 132     }
 133 
 134     static void testBulkRevocation() throws Throwable {
 135         class MyLock {};
 136 
 137         Recording recording = new Recording();
 138 
 139         recording.enable(EventNames.BiasedLockClassRevocation);
 140         recording.start();
 141 
 142         Thread biasBreaker = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 143 
 144         recording.stop();
 145         List<RecordedEvent> events = getRevocationEvents(recording, "revokedClass", MyLock.class);
 146         Asserts.assertEQ(events.size(), 1);
 147 
 148         RecordedEvent event = events.get(0);
 149         Events.assertEventThread(event, biasBreaker);
 150         Events.assertField(event, "disableBiasing").equal(false);
 151 
 152         RecordedClass lockClass = event.getValue("revokedClass");
 153         Asserts.assertEquals(lockClass.getName(), MyLock.class.getName());
 154 
 155         validateStackTrace(event.getStackTrace());
 156     }
 157 
 158     static void testSelfRevocation() throws Throwable {
 159         class MyLock {};
 160 
 161         Recording recording = new Recording();
 162 
 163         recording.enable(EventNames.BiasedLockSelfRevocation);
 164         recording.start();
 165 
 166         MyLock l = new MyLock();
 167         touch(l);
 168         Thread.holdsLock(l);
 169 
 170         recording.stop();
 171         List<RecordedEvent> events = getRevocationEvents(recording, "lockClass", MyLock.class);
 172         Asserts.assertEQ(events.size(), 1);
 173 
 174         RecordedEvent event = events.get(0);
 175         Events.assertEventThread(event, Thread.currentThread());
 176 
 177         validateStackTrace(event.getStackTrace(), "holdsLock");
 178     }
 179 
 180     static void testExitedThreadRevocation() throws Throwable {
 181         class MyLock {};
 182 
 183         Recording recording = new Recording();
 184 
 185         recording.enable(EventNames.BiasedLockRevocation);
 186         recording.start();
 187 
 188         FutureTask<MyLock> lockerTask = new FutureTask<>(() -> {
 189            MyLock l = new MyLock();
 190            touch(l);
 191            return l;
 192         });
 193 
 194         Thread locker = new Thread(lockerTask, "BiasLocker");
 195         locker.start();
 196         locker.join();
 197 
 198         // Even after joining, the VM has a bit more work to do before the thread is actually removed
 199         // from the threads list. Ensure that this has happened before proceeding.
 200         while (true) {
 201             PidJcmdExecutor jcmd = new PidJcmdExecutor();
 202             OutputAnalyzer oa = jcmd.execute("Thread.print", true);
 203             String lockerThreadFound = oa.firstMatch("BiasLocker");
 204             if (lockerThreadFound == null) {
 205                 break;
 206             }
 207         };
 208 
 209         MyLock l = lockerTask.get();
 210         touch(l);
 211 
 212         recording.stop();
 213         List<RecordedEvent> events = getRevocationEvents(recording, "lockClass", MyLock.class);
 214         Asserts.assertEQ(events.size(), 1);
 215 
 216         RecordedEvent event = events.get(0);
 217         Events.assertEventThread(event, Thread.currentThread());
 218         // Previous owner will usually be null, but can also be a thread that
 219         // was created after the BiasLocker thread exited due to address reuse.
 220         RecordedThread prevOwner = event.getValue("previousOwner");
 221         if (prevOwner != null) {
 222             Asserts.assertNE(prevOwner.getJavaName(), "BiasLocker");
 223         }
 224         validateStackTrace(event.getStackTrace());
 225     }
 226 
 227     static void testBulkRevocationNoRebias() throws Throwable {
 228         class MyLock {};
 229 
 230         Recording recording = new Recording();
 231 
 232         recording.enable(EventNames.BiasedLockClassRevocation);
 233         recording.start();
 234 
 235         Thread biasBreaker0 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 236         Thread biasBreaker1 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 237 
 238         recording.stop();
 239         List<RecordedEvent> events = getRevocationEvents(recording, "revokedClass", MyLock.class);
 240         Asserts.assertEQ(events.size(), 2);
 241 
 242         // The rebias event should occur before the noRebias one
 243         RecordedEvent eventRebias = events.get(0);
 244         RecordedEvent eventNoRebias = events.get(1);
 245 
 246         Events.assertEventThread(eventRebias, biasBreaker0);
 247         Events.assertField(eventRebias, "disableBiasing").equal(false);
 248 
 249         Events.assertEventThread(eventNoRebias, biasBreaker1);
 250         Events.assertField(eventNoRebias, "disableBiasing").equal(true);
 251 
 252         RecordedClass lockClassRebias = eventRebias.getValue("revokedClass");
 253         Asserts.assertEquals(lockClassRebias.getName(), MyLock.class.getName());
 254         RecordedClass lockClassNoRebias = eventNoRebias.getValue("revokedClass");
 255         Asserts.assertEquals(lockClassNoRebias.getName(), MyLock.class.getName());
 256 
 257         validateStackTrace(eventRebias.getStackTrace());
 258         validateStackTrace(eventNoRebias.getStackTrace());
 259     }
 260 
 261     static void testRevocationSafepointIdCorrelation() throws Throwable {
 262         class MyLock {};
 263 
 264         Recording recording = new Recording();
 265 
 266         recording.enable(EventNames.BiasedLockRevocation);
 267         recording.enable(EventNames.BiasedLockClassRevocation);
 268         recording.enable(EventNames.ExecuteVMOperation);
 269         recording.start();
 270 
 271         triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 272 
 273         recording.stop();
 274         List<RecordedEvent> events = Events.fromRecording(recording);
 275 
 276         // Determine which safepoints included single and bulk revocation VM operations
 277         Set<Integer> vmOperationsSingle = new HashSet<>();
 278         Set<Integer> vmOperationsBulk = new HashSet<>();
 279 
 280         for (RecordedEvent event : events) {
 281             if (event.getEventType().getName().equals(EventNames.ExecuteVMOperation)) {
 282                 String operation = event.getValue("operation");
 283                 Integer safepointId = event.getValue("safepointId");
 284 
 285                 if (operation.equals("RevokeBias")) {
 286                     vmOperationsSingle.add(safepointId);
 287                 } else if (operation.equals("BulkRevokeBias")) {
 288                     vmOperationsBulk.add(safepointId);
 289                 }
 290             }
 291         }
 292 
 293         int revokeCount = 0;
 294         int bulkRevokeCount = 0;
 295 
 296         // Match all revoke events to a corresponding VMOperation event
 297         for (RecordedEvent event : events) {
 298             if (event.getEventType().getName().equals(EventNames.BiasedLockRevocation)) {
 299                 Integer safepointId = event.getValue("safepointId");
 300                 String lockClass = ((RecordedClass)event.getValue("lockClass")).getName();
 301                 if (lockClass.equals(MyLock.class.getName())) {
 302                     Asserts.assertTrue(vmOperationsSingle.contains(safepointId));
 303                     revokeCount++;
 304                 }
 305             } else if (event.getEventType().getName().equals(EventNames.BiasedLockClassRevocation)) {
 306                 Integer safepointId = event.getValue("safepointId");
 307                 String lockClass = ((RecordedClass)event.getValue("revokedClass")).getName();
 308                 if (lockClass.toString().equals(MyLock.class.getName())) {
 309                     Asserts.assertTrue(vmOperationsBulk.contains(safepointId));
 310                     bulkRevokeCount++;
 311                 }
 312             }
 313         }
 314 
 315         Asserts.assertGT(bulkRevokeCount, 0);
 316         Asserts.assertGT(revokeCount, bulkRevokeCount);
 317     }
 318 }