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  * @requires vm.hasJFR
44  * @library /test/lib
45  *
46  * @run main/othervm jdk.jfr.event.runtime.TestBiasedLockRevocationEvents
47  */
48 public class TestBiasedLockRevocationEvents {
49 
main(String[] args)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 
touch(Object lock)63     static void touch(Object lock) {
64         synchronized(lock) {
65         }
66     }
67 
triggerRevocation(int numRevokes, Class<?> lockClass)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
validateStackTrace(RecordedStackTrace stackTrace, String leafMethodName)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
validateStackTrace(RecordedStackTrace stackTrace)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
getRevocationEvents(Recording recording, String fieldName, Class<?> lockClass)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 
testSingleRevocation()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 
testBulkRevocation()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 
testSelfRevocation()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 
testExitedThreadRevocation()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 
testBulkRevocationNoRebias()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 
testRevocationSafepointIdCorrelation()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<Long> vmOperationsSingle = new HashSet<>();
279         Set<Long> vmOperationsBulk = new HashSet<>();
280 
281         for (RecordedEvent event : events) {
282             if (event.getEventType().getName().equals(EventNames.ExecuteVMOperation)) {
283                 String operation = event.getValue("operation");
284                 Long 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                 Long 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                 Long 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 }
320