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  * 
  44  * @library /lib /
  45  *
  46  * @run main/othervm -XX:BiasedLockingStartupDelay=0 jdk.jfr.event.runtime.TestBiasedLockRevocationEvents
  47  */
  48 public class TestBiasedLockRevocationEvents {
  49 
  50     public static void main(String[] args) throws Throwable {
  51         testSingleRevocation();
  52         testBulkRevocation();
  53         testSelfRevocation();
  54         testExitedThreadRevocation();
  55         testBulkRevocationNoRebias();
  56         testRevocationSafepointIdCorrelation();
  57     }
  58 
  59     // Default value of BiasedLockingBulkRebiasThreshold is 20, and BiasedLockingBulkRevokeTreshold is 40.
  60     // Using a value that will hit the first threshold once, and the second one the next time.
  61     private static final int BULK_REVOKE_THRESHOLD = 25;
  62 
  63     static void touch(Object lock) {
  64         synchronized(lock) {
  65         }
  66     }
  67 
  68     static Thread triggerRevocation(int numRevokes, Class<?> lockClass) throws Throwable {
  69         Object[] locks = new Object[numRevokes];
  70         for (int i = 0; i < locks.length; ++i) {
  71             locks[i] = lockClass.getDeclaredConstructor().newInstance();
  72             touch(locks[i]);
  73         }
  74 
  75         Thread biasBreaker = new Thread("BiasBreaker") {
  76             @Override
  77             public void run() {
  78                 for (Object lock : locks) {
  79                     touch(lock);
  80                 }
  81             }
  82         };
  83 
  84         biasBreaker.start();
  85         biasBreaker.join();
  86 
  87         return biasBreaker;
  88     }
  89 
  90     // Basic stack trace validation, checking the name of the leaf method
  91     static void validateStackTrace(RecordedStackTrace stackTrace, String leafMethodName) {
  92         List<RecordedFrame> frames = stackTrace.getFrames();
  93         Asserts.assertFalse(frames.isEmpty());
  94         String name = frames.get(0).getMethod().getName();
  95         Asserts.assertEquals(name, leafMethodName);
  96     }
  97 
  98     // Validates that the given stack trace refers to lock.touch(); in triggerRevocation
  99     static void validateStackTrace(RecordedStackTrace stackTrace) {
 100         validateStackTrace(stackTrace, "touch");
 101     }
 102 
 103     // Retrieve all biased lock revocation events related to the provided lock class, sorted by start time
 104     static List<RecordedEvent> getRevocationEvents(Recording recording, String fieldName, Class<?> lockClass) throws Throwable {
 105         return Events.fromRecording(recording).stream()
 106                 .filter(e -> ((RecordedClass)e.getValue(fieldName)).getName().equals(lockClass.getName()))
 107                 .sorted(Comparator.comparing(RecordedEvent::getStartTime))
 108                 .collect(Collectors.toList());
 109     }
 110 
 111     static void testSingleRevocation() throws Throwable {
 112         class MyLock {};
 113 
 114         Recording recording = new Recording();
 115 
 116         recording.enable(EventNames.BiasedLockRevocation);
 117         recording.start();
 118 
 119         Thread biasBreaker = triggerRevocation(1, MyLock.class);
 120 
 121         recording.stop();
 122         List<RecordedEvent> events = getRevocationEvents(recording, "lockClass", MyLock.class);
 123         Asserts.assertEQ(events.size(), 1);
 124 
 125         RecordedEvent event = events.get(0);
 126         Events.assertEventThread(event, biasBreaker);
 127         Events.assertEventThread(event, "previousOwner", Thread.currentThread());
 128 
 129         RecordedClass lockClass = event.getValue("lockClass");
 130         Asserts.assertEquals(lockClass.getName(), MyLock.class.getName());
 131 
 132         validateStackTrace(event.getStackTrace());
 133     }
 134 
 135     static void testBulkRevocation() throws Throwable {
 136         class MyLock {};
 137 
 138         Recording recording = new Recording();
 139 
 140         recording.enable(EventNames.BiasedLockClassRevocation);
 141         recording.start();
 142 
 143         Thread biasBreaker = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 144 
 145         recording.stop();
 146         List<RecordedEvent> events = getRevocationEvents(recording, "revokedClass", MyLock.class);
 147         Asserts.assertEQ(events.size(), 1);
 148 
 149         RecordedEvent event = events.get(0);
 150         Events.assertEventThread(event, biasBreaker);
 151         Events.assertField(event, "disableBiasing").equal(false);
 152 
 153         RecordedClass lockClass = event.getValue("revokedClass");
 154         Asserts.assertEquals(lockClass.getName(), MyLock.class.getName());
 155 
 156         validateStackTrace(event.getStackTrace());
 157     }
 158 
 159     static void testSelfRevocation() throws Throwable {
 160         class MyLock {};
 161 
 162         Recording recording = new Recording();
 163 
 164         recording.enable(EventNames.BiasedLockSelfRevocation);
 165         recording.start();
 166 
 167         MyLock l = new MyLock();
 168         touch(l);
 169         Thread.holdsLock(l);
 170 
 171         recording.stop();
 172         List<RecordedEvent> events = getRevocationEvents(recording, "lockClass", MyLock.class);
 173         Asserts.assertEQ(events.size(), 1);
 174 
 175         RecordedEvent event = events.get(0);
 176         Events.assertEventThread(event, Thread.currentThread());
 177 
 178         validateStackTrace(event.getStackTrace(), "holdsLock");
 179     }
 180 
 181     static void testExitedThreadRevocation() throws Throwable {
 182         class MyLock {};
 183 
 184         Recording recording = new Recording();
 185 
 186         recording.enable(EventNames.BiasedLockRevocation);
 187         recording.start();
 188 
 189         FutureTask<MyLock> lockerTask = new FutureTask<>(() -> {
 190            MyLock l = new MyLock();
 191            touch(l);
 192            return l;
 193         });
 194 
 195         Thread locker = new Thread(lockerTask, "BiasLocker");
 196         locker.start();
 197         locker.join();
 198 
 199         // Even after joining, the VM has a bit more work to do before the thread is actually removed
 200         // from the threads list. Ensure that this has happened before proceeding.
 201         while (true) {
 202             PidJcmdExecutor jcmd = new PidJcmdExecutor();
 203             OutputAnalyzer oa = jcmd.execute("Thread.print", true);
 204             String lockerThreadFound = oa.firstMatch("BiasLocker");
 205             if (lockerThreadFound == null) {
 206                 break;
 207             }
 208         };
 209 
 210         MyLock l = lockerTask.get();
 211         touch(l);
 212 
 213         recording.stop();
 214         List<RecordedEvent> events = getRevocationEvents(recording, "lockClass", MyLock.class);
 215         Asserts.assertEQ(events.size(), 1);
 216 
 217         RecordedEvent event = events.get(0);
 218         Events.assertEventThread(event, Thread.currentThread());
 219         // Previous owner will usually be null, but can also be a thread that
 220         // was created after the BiasLocker thread exited due to address reuse.
 221         RecordedThread prevOwner = event.getValue("previousOwner");
 222         if (prevOwner != null) {
 223             Asserts.assertNE(prevOwner.getJavaName(), "BiasLocker");
 224         }
 225         validateStackTrace(event.getStackTrace());
 226     }
 227 
 228     static void testBulkRevocationNoRebias() throws Throwable {
 229         class MyLock {};
 230 
 231         Recording recording = new Recording();
 232 
 233         recording.enable(EventNames.BiasedLockClassRevocation);
 234         recording.start();
 235 
 236         Thread biasBreaker0 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 237         Thread biasBreaker1 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
 238 
 239         recording.stop();
 240         List<RecordedEvent> events = getRevocationEvents(recording, "revokedClass", MyLock.class);
 241         Asserts.assertEQ(events.size(), 2);
 242 
 243         // The rebias event should occur before the noRebias one
 244         RecordedEvent eventRebias = events.get(0);
 245         RecordedEvent eventNoRebias = events.get(1);
 246 
 247         Events.assertEventThread(eventRebias, biasBreaker0);
 248         Events.assertField(eventRebias, "disableBiasing").equal(false);
 249 
 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         // Determine which safepoints included single and bulk revocation VM operations
 278         Set<Integer> vmOperationsSingle = new HashSet<>();
 279         Set<Integer> vmOperationsBulk = new HashSet<>();
 280 
 281         for (RecordedEvent event : events) {
 282             if (event.getEventType().getName().equals(EventNames.ExecuteVMOperation)) {
 283                 String operation = event.getValue("operation");
 284                 Integer safepointId = event.getValue("safepointId");
 285 
 286                 if (operation.equals("RevokeBias")) {
 287                     vmOperationsSingle.add(safepointId);
 288                 } else if (operation.equals("BulkRevokeBias")) {
 289                     vmOperationsBulk.add(safepointId);
 290                 }
 291             }
 292         }
 293 
 294         int revokeCount = 0;
 295         int bulkRevokeCount = 0;
 296 
 297         // Match all revoke events to a corresponding VMOperation event
 298         for (RecordedEvent event : events) {
 299             if (event.getEventType().getName().equals(EventNames.BiasedLockRevocation)) {
 300                 Integer safepointId = event.getValue("safepointId");
 301                 String lockClass = ((RecordedClass)event.getValue("lockClass")).getName();
 302                 if (lockClass.equals(MyLock.class.getName())) {
 303                     Asserts.assertTrue(vmOperationsSingle.contains(safepointId));
 304                     revokeCount++;
 305                 }
 306             } else if (event.getEventType().getName().equals(EventNames.BiasedLockClassRevocation)) {
 307                 Integer safepointId = event.getValue("safepointId");
 308                 String lockClass = ((RecordedClass)event.getValue("revokedClass")).getName();
 309                 if (lockClass.toString().equals(MyLock.class.getName())) {
 310                     Asserts.assertTrue(vmOperationsBulk.contains(safepointId));
 311                     bulkRevokeCount++;
 312                 }
 313             }
 314         }
 315 
 316         Asserts.assertGT(bulkRevokeCount, 0);
 317         Asserts.assertGT(revokeCount, bulkRevokeCount);
 318     }
 319 }