blob: dc12e6ec2f0835dd29f444fc88457e5e1968b9e2 [file] [log] [blame]
/*
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jfr.event.runtime;
import jdk.jfr.Recording;
import jdk.jfr.consumer.*;
import jdk.test.lib.Asserts;
import jdk.test.lib.dcmd.PidJcmdExecutor;
import jdk.test.lib.jfr.EventNames;
import jdk.test.lib.jfr.Events;
import jdk.test.lib.process.OutputAnalyzer;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.FutureTask;
/*
* @test
* @key jfr
* @library /test/lib
*
* @run main/othervm jdk.jfr.event.runtime.TestBiasedLockRevocationEvents
*/
public class TestBiasedLockRevocationEvents {
public static void main(String[] args) throws Throwable {
testSingleRevocation();
testBulkRevocation();
testSelfRevocation();
testExitedThreadRevocation();
testBulkRevocationNoRebias();
testRevocationSafepointIdCorrelation();
}
// Default value of BiasedLockingBulkRebiasThreshold is 20, and BiasedLockingBulkRevokeTreshold is 40.
// Using a value that will hit the first threshold once, and the second one the next time.
private static final int BULK_REVOKE_THRESHOLD = 25;
static void touch(Object lock) {
synchronized(lock) {
}
}
static Thread triggerRevocation(int numRevokes, Class<?> lockClass) throws Throwable {
Object[] locks = new Object[numRevokes];
for (int i = 0; i < locks.length; ++i) {
locks[i] = lockClass.getDeclaredConstructor().newInstance();
touch(locks[i]);
}
Thread biasBreaker = new Thread("BiasBreaker") {
@Override
public void run() {
for (Object lock : locks) {
touch(lock);
}
}
};
biasBreaker.start();
biasBreaker.join();
return biasBreaker;
}
// Basic stack trace validation, checking the name of the leaf method
static void validateStackTrace(RecordedStackTrace stackTrace, String leafMethodName) {
List<RecordedFrame> frames = stackTrace.getFrames();
Asserts.assertFalse(frames.isEmpty());
String name = frames.get(0).getMethod().getName();
Asserts.assertEquals(name, leafMethodName);
}
// Validates that the given stack trace refers to lock.touch(); in triggerRevocation
static void validateStackTrace(RecordedStackTrace stackTrace) {
validateStackTrace(stackTrace, "touch");
}
static void testSingleRevocation() throws Throwable {
class MyLock {};
Recording recording = new Recording();
recording.enable(EventNames.BiasedLockRevocation);
recording.start();
Thread biasBreaker = triggerRevocation(1, MyLock.class);
recording.stop();
List<RecordedEvent> events = Events.fromRecording(recording);
// We may or may not catch a second revocation from the biasBreaker thread exiting
Asserts.assertGreaterThanOrEqual(events.size(), 1);
RecordedEvent event = events.get(0);
Events.assertEventThread(event, biasBreaker);
Events.assertEventThread(event, "previousOwner", Thread.currentThread());
RecordedClass lockClass = event.getValue("lockClass");
Asserts.assertEquals(lockClass.getName(), MyLock.class.getName());
validateStackTrace(event.getStackTrace());
}
static void testBulkRevocation() throws Throwable {
class MyLock {};
Recording recording = new Recording();
recording.enable(EventNames.BiasedLockClassRevocation);
recording.start();
Thread biasBreaker = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
recording.stop();
List<RecordedEvent> events = Events.fromRecording(recording);
Asserts.assertEQ(events.size(), 1);
RecordedEvent event = events.get(0);
Events.assertEventThread(event, biasBreaker);
Events.assertField(event, "disableBiasing").equal(false);
RecordedClass lockClass = event.getValue("revokedClass");
Asserts.assertEquals(lockClass.getName(), MyLock.class.getName());
validateStackTrace(event.getStackTrace());
}
static void testSelfRevocation() throws Throwable {
class MyLock {};
Recording recording = new Recording();
recording.enable(EventNames.BiasedLockSelfRevocation);
recording.start();
MyLock l = new MyLock();
touch(l);
Thread.holdsLock(l);
recording.stop();
List<RecordedEvent> events = Events.fromRecording(recording);
Asserts.assertEQ(events.size(), 1);
RecordedEvent event = events.get(0);
Events.assertEventThread(event, Thread.currentThread());
validateStackTrace(event.getStackTrace(), "holdsLock");
}
static void testExitedThreadRevocation() throws Throwable {
class MyLock {};
Recording recording = new Recording();
recording.enable(EventNames.BiasedLockRevocation);
recording.start();
FutureTask<MyLock> lockerTask = new FutureTask<>(() -> {
MyLock l = new MyLock();
touch(l);
return l;
});
Thread locker = new Thread(lockerTask, "BiasLocker");
locker.start();
locker.join();
// Even after joining, the VM has a bit more work to do before the thread is actually removed
// from the threads list. Ensure that this has happened before proceeding.
while (true) {
PidJcmdExecutor jcmd = new PidJcmdExecutor();
OutputAnalyzer oa = jcmd.execute("Thread.print", true);
String lockerThreadFound = oa.firstMatch("BiasLocker");
if (lockerThreadFound == null) {
break;
}
};
MyLock l = lockerTask.get();
touch(l);
recording.stop();
List<RecordedEvent> events = Events.fromRecording(recording);
Events.hasEvents(events);
// Joining the locker thread can cause revocations as well, search for the interesting one
for (RecordedEvent event : events) {
RecordedClass lockClass = event.getValue("lockClass");
if (lockClass.getName().equals(MyLock.class.getName())) {
Events.assertEventThread(event, Thread.currentThread());
// Previous owner will usually be null, but can also be a thread that
// was created after the BiasLocker thread exited due to address reuse.
RecordedThread prevOwner = event.getValue("previousOwner");
if (prevOwner != null) {
Asserts.assertNE(prevOwner.getJavaName(), "BiasLocker");
}
validateStackTrace(event.getStackTrace());
return;
}
}
Asserts.fail("Did not find any revocation event for MyLock");
}
static void testBulkRevocationNoRebias() throws Throwable {
class MyLock {};
Recording recording = new Recording();
recording.enable(EventNames.BiasedLockClassRevocation);
recording.start();
Thread biasBreaker0 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
Thread biasBreaker1 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
recording.stop();
List<RecordedEvent> events = Events.fromRecording(recording);
Asserts.assertEQ(events.size(), 2);
// The rebias event should occur before the noRebias one
RecordedEvent eventRebias = events.get(0).getStartTime().isBefore(events.get(1).getStartTime()) ? events.get(0) : events.get(1);
RecordedEvent eventNoRebias = events.get(0).getStartTime().isBefore(events.get(1).getStartTime()) ? events.get(1) : events.get(0);
Events.assertEventThread(eventRebias, biasBreaker0);
Events.assertField(eventRebias, "disableBiasing").equal(false);
Events.assertEventThread(eventNoRebias, biasBreaker1);
Events.assertField(eventNoRebias, "disableBiasing").equal(true);
RecordedClass lockClassRebias = eventRebias.getValue("revokedClass");
Asserts.assertEquals(lockClassRebias.getName(), MyLock.class.getName());
RecordedClass lockClassNoRebias = eventNoRebias.getValue("revokedClass");
Asserts.assertEquals(lockClassNoRebias.getName(), MyLock.class.getName());
validateStackTrace(eventRebias.getStackTrace());
validateStackTrace(eventNoRebias.getStackTrace());
}
static void testRevocationSafepointIdCorrelation() throws Throwable {
class MyLock {};
Recording recording = new Recording();
recording.enable(EventNames.BiasedLockRevocation);
recording.enable(EventNames.BiasedLockClassRevocation);
recording.enable(EventNames.ExecuteVMOperation);
recording.start();
triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
recording.stop();
List<RecordedEvent> events = Events.fromRecording(recording);
// Find all biased locking related VMOperation events
HashMap<Integer, RecordedEvent> vmOperations = new HashMap<Integer, RecordedEvent>();
for (RecordedEvent event : events) {
if ((event.getEventType().getName().equals(EventNames.ExecuteVMOperation)) &&
(event.getValue("operation").toString().contains("Bias"))) {
vmOperations.put(event.getValue("safepointId"), event);
}
}
int revokeCount = 0;
int bulkRevokeCount = 0;
// Match all revoke events to a corresponding VMOperation event
for (RecordedEvent event : events) {
if (event.getEventType().getName().equals(EventNames.BiasedLockRevocation)) {
RecordedEvent vmOpEvent = vmOperations.remove(event.getValue("safepointId"));
if (event.getValue("safepointId").toString().equals("-1")) {
Asserts.assertEquals(vmOpEvent, null);
} else {
Events.assertField(vmOpEvent, "operation").equal("RevokeBias");
revokeCount++;
}
} else if (event.getEventType().getName().equals(EventNames.BiasedLockClassRevocation)) {
RecordedEvent vmOpEvent = vmOperations.remove(event.getValue("safepointId"));
Events.assertField(vmOpEvent, "operation").equal("BulkRevokeBias");
bulkRevokeCount++;
}
}
// All VMOperations should have had a matching revoke event
Asserts.assertEQ(vmOperations.size(), 0);
Asserts.assertGT(bulkRevokeCount, 0);
Asserts.assertGT(revokeCount, bulkRevokeCount);
}
}