From 481f81c3e7c66836d1d2ed3ff13e6da4a604a32c Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 9 Sep 2022 16:41:19 -0400 Subject: [PATCH 01/21] add JFR event unit tests for thread sleep and monitor enter --- .../src/com/oracle/svm/test/jfr/JfrTest.java | 47 +++++++++++ .../oracle/svm/test/jfr/TestThreadSleep.java | 79 +++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java index a75203702ec4..1b160623377c 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java @@ -28,7 +28,15 @@ import static org.junit.Assume.assumeTrue; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; +import java.util.List; import org.graalvm.nativeimage.ImageInfo; import org.graalvm.nativeimage.hosted.Feature; @@ -51,6 +59,8 @@ public abstract class JfrTest { protected Jfr jfr; protected Recording recording; + private ChronologicalComparator chronologicalComparator = new ChronologicalComparator(); + protected final long MS_TOLERANCE = 10; @BeforeClass public static void checkForJFR() { @@ -76,6 +86,7 @@ public void startRecording() { public void endRecording() { try { jfr.endRecording(recording); + analyzeEvents(); } catch (Exception e) { Assert.fail("Fail to stop recording! Cause: " + e.getMessage()); } @@ -100,6 +111,8 @@ protected void enableEvents(String[] events) { } public abstract String[] getTestedEvents(); + public void analyzeEvents(){ + } protected void checkEvents() { HashSet seenEvents = new HashSet<>(); @@ -127,6 +140,40 @@ protected void checkRecording() throws AssertionError { Assert.fail("Failed to parse recording: " + e.getMessage()); } } + + private static class ChronologicalComparator implements Comparator { + @Override + public int compare(RecordedEvent e1, RecordedEvent e2) { + return e1.getStartTime().compareTo(e2.getStartTime()); + } + } + private Path makeCopy(Recording recording, String testName) throws IOException { // from jdk 19 + Path p = recording.getDestination(); + if (p == null) { + File directory = new File("."); + p = new File(directory.getAbsolutePath(), "recording-" + recording.getId() + "-" + testName+ ".jfr").toPath(); + recording.dump(p); + } + return p; + } + protected List getEvents(Recording recording, String testName) throws IOException { + Path p = makeCopy(recording, testName); + List events = RecordingFile.readAllEvents(p); + Collections.sort(events, chronologicalComparator); + return events; + } + + + /** Used for comparing durations with a tolerance of MS_TOLERANCE */ + protected boolean isEqualDuration(Duration d1, Duration d2) { + return d1.minus(d2).abs().compareTo(Duration.ofMillis(MS_TOLERANCE)) < 0; + } + + /** Used for comparing durations with a tolerance of MS_TOLERANCE. True if 'larger' really is bigger */ + protected boolean isGreaterDuration(Duration smaller, Duration larger) { + return smaller.minus(larger.plus(Duration.ofMillis(MS_TOLERANCE))).isNegative(); + } + } class JFRTestFeature implements Feature { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java new file mode 100644 index 000000000000..aed5475b2ae5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2022, Red Hat Inc. 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 com.oracle.svm.test.jfr; + +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedObject; +import jdk.jfr.consumer.RecordedThread; + +import java.io.IOException; +import java.time.Duration; +import java.util.List; + +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import com.oracle.svm.test.jfr.JfrTest; + +public class TestThreadSleep extends JfrTest { + private String sleepingThreadName; + private static final int MILLIS = 50; + + @Override + public String[] getTestedEvents() { + return new String[]{"jdk.ThreadSleep"}; + } + @Override + public void analyzeEvents() { + List events; + try { + events = getEvents(recording, "jdk.ThreadSleep"); + } catch (IOException e) { + throw new RuntimeException(e); + } + boolean foundSleepEvent = false; + for (RecordedEvent event : events) { + RecordedObject struct = event; + String eventThread = struct.getValue("eventThread").getJavaName(); + if (!eventThread.equals(sleepingThreadName)) { + continue; + } + assertTrue("wrong event type",event.getEventType().getName().equals("jdk.ThreadSleep")); + + assertTrue("Slept wrong duration.",isEqualDuration(event.getDuration(), Duration.ofMillis(MILLIS))); + foundSleepEvent = true; + break; + } + assertTrue("Sleep event not found.", foundSleepEvent); + } + + + @Test + public void test() throws Exception { + sleepingThreadName = Thread.currentThread().getName(); + Thread.sleep(MILLIS); + } +} From d0f03cf1a4846c6de34825cc52a43b15c202892b Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 12 Sep 2022 10:59:50 -0400 Subject: [PATCH 02/21] add java monitor wait --- .../svm/test/jfr/TestJavaMonitorWait.java | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java new file mode 100644 index 000000000000..3b6eec4f0779 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2022, Red Hat Inc. 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 com.oracle.svm.test.jfr; + +import static java.lang.Math.abs; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.time.Duration; +import java.util.List; + +import jdk.jfr.consumer.RecordedClass; +import org.junit.Test; + +import com.oracle.svm.test.jfr.JfrTest; + +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedObject; +import jdk.jfr.consumer.RecordedThread; + +public class TestJavaMonitorWait extends JfrTest { + private static final int MILLIS = 50; + private static final int COUNT = 10; + private String producerName; + private String consumerName; + static Helper helper = new Helper(); + + @Override + public String[] getTestedEvents() { + return new String[]{"jdk.JavaMonitorWait"}; + } + @Override + public void analyzeEvents() { + List events; + try { + events = getEvents(recording, "jdk.JavaMonitorWait"); + } catch (IOException e) { + throw new RuntimeException(e); + } + + int prodCount = 0; + int consCount = 0; + String lastEventThreadName = null; //should alternate if buffer is 1 + for (RecordedEvent event : events) { + RecordedObject struct = event; + String eventThread = struct.getValue("eventThread").getJavaName(); + String notifThread = struct.getValue("notifier") != null ? struct.getValue("notifier").getJavaName() : null; + assertTrue("No event thread",eventThread != null); + if (!eventThread.equals(producerName) && !eventThread.equals(consumerName) ) { + continue; + } + assertTrue( "Wrong event type", event.getEventType().getName().equals("jdk.JavaMonitorWait")); + assertTrue("Wrong event duration", isEqualDuration(Duration.ofMillis(MILLIS), event.getDuration())); + assertFalse("Wrong monitor class.", + !struct.getValue("monitorClass").getName().equals(Helper.class.getName()) + && (eventThread.equals(consumerName) ||eventThread.equals(producerName))); + + assertFalse("Should not have timed out.", struct.getValue("timedOut").booleanValue()); + + if (lastEventThreadName == null) { + lastEventThreadName = notifThread; + } + assertTrue("Not alternating", lastEventThreadName.equals(notifThread)); + if (eventThread.equals(producerName)) { + prodCount++; + assertTrue("Wrong notifier", notifThread.equals(consumerName)); + } else if (eventThread.equals(consumerName)) { + consCount++; + assertTrue("Wrong notifier", notifThread.equals(producerName)); + } + lastEventThreadName = eventThread; + } + assertFalse("Wrong number of events: "+prodCount + " "+consCount, + abs(prodCount - consCount) > 1 || abs(consCount-COUNT) >1); + } + + + @Test + public void test() throws Exception { + Runnable consumer = () -> { + try { + helper.consume(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + Runnable producer = () -> { + try { + helper.produce(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + Thread tc = new Thread(consumer); + Thread tp = new Thread(producer); + producerName = tp.getName(); + consumerName = tc.getName(); + tp.start(); + tc.start(); + tp.join(); + tc.join(); + + // sleep so we know the event is recorded + Thread.sleep(500); + } + + static class Helper { + private int count = 0; + private final int bufferSize = 1; + + public synchronized void produce() throws InterruptedException { + for (int i = 0; i< COUNT; i++) { + while (count >= bufferSize) { + wait(); + } + Thread.sleep(MILLIS); + count++; + notify(); + } + } + + public synchronized void consume() throws InterruptedException { + for (int i = 0; i< COUNT; i++) { + while (count == 0) { + wait(); + } + Thread.sleep(MILLIS); + count--; + notify(); + } + } + } +} From 98e3ac946daa3ad03595936bf1c57f081de37988 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 12 Sep 2022 14:17:26 -0400 Subject: [PATCH 03/21] make tests more robust --- .../src/com/oracle/svm/test/jfr/JfrTest.java | 2 +- .../src/com/oracle/svm/test/jfr/Stressor.java | 47 +++++ .../svm/test/jfr/TestJavaMonitorEnter.java | 115 ++++++++++++ .../svm/test/jfr/TestJavaMonitorWait.java | 11 +- .../test/jfr/TestJavaMonitorWaitTimeout.java | 176 ++++++++++++++++++ .../oracle/svm/test/jfr/TestThreadSleep.java | 10 +- 6 files changed, 350 insertions(+), 11 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java index 1b160623377c..d776f6ba91d7 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java @@ -144,7 +144,7 @@ protected void checkRecording() throws AssertionError { private static class ChronologicalComparator implements Comparator { @Override public int compare(RecordedEvent e1, RecordedEvent e2) { - return e1.getStartTime().compareTo(e2.getStartTime()); + return e1.getEndTime().compareTo(e2.getEndTime()); } } private Path makeCopy(Recording recording, String testName) throws IOException { // from jdk 19 diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java new file mode 100644 index 000000000000..29121ce12e56 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2022, Red Hat Inc. 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 com.oracle.svm.test.jfr; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class to help run multiple threads executing some task + */ +public class Stressor { + public static void execute(int numberOfThreads, Thread.UncaughtExceptionHandler eh, Runnable task) throws Exception { + List threads = new ArrayList<>(); + for (int n = 0; n < numberOfThreads; ++n) { + Thread t = new Thread(task); + t.setUncaughtExceptionHandler(eh); + threads.add(t); + t.start(); + } + for (Thread t : threads) { + t.join(); + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java new file mode 100644 index 000000000000..0e4f02f197f4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2022, Red Hat Inc. 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 com.oracle.svm.test.jfr; + +import static java.lang.Math.abs; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import jdk.jfr.consumer.RecordedThread; +import org.junit.Test; + +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedObject; +import com.oracle.svm.test.jfr.JfrTest; + +import com.oracle.svm.test.jfr.Stressor; + +public class TestJavaMonitorEnter extends JfrTest { + private final int THREADS = 10; + private static final int MILLIS = 60; + static Object monitor = new Object(); + + private static Queue orderedWaiterNames = new LinkedList<>(); + @Override + public String[] getTestedEvents() { + return new String[]{"jdk.JavaMonitorEnter"}; + } + @Override + public void analyzeEvents() { + List events; + try { + events = getEvents(recording, "jdk.JavaMonitorEnter"); + } catch (IOException e) { + throw new RuntimeException(e); + } + int count = 0; + orderedWaiterNames.poll(); //first worker does not wait + String waiterName = orderedWaiterNames.poll(); + Long prev = 0L; + for (RecordedEvent event : events) { + RecordedObject struct = event; + String eventThread = struct.getValue("eventThread").getJavaName(); + if (event.getEventType().getName().equals("jdk.JavaMonitorEnter") + && isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration()) + && waiterName.equals(eventThread)) { + Long duration = event.getDuration().toMillis(); + assertTrue( "Durations not as expected ", abs(duration - prev - MILLIS) < MS_TOLERANCE ); + count++; + waiterName = orderedWaiterNames.poll(); + prev = duration; + } + } + assertTrue("Wrong number of Java Monitor Enter Events " + count, count == THREADS - 1);// -1 because first thread does not get blocked by any previous thread + } + + private static void doWork(Object obj) throws InterruptedException { + synchronized(obj){ + Thread.sleep(MILLIS); + orderedWaiterNames.add(Thread.currentThread().getName()); + } + } + @Test + public void test() throws Exception { + int threadCount = THREADS; + Runnable r = () -> { + // create contention between threads for one lock + try { + doWork(monitor); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + }; + Thread.UncaughtExceptionHandler eh = (t, e) -> e.printStackTrace(); + + try { + Stressor.execute(threadCount, eh, r); + // sleep so we know the event is recorded + Thread.sleep(500); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java index 3b6eec4f0779..5ba28776182c 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java @@ -71,15 +71,14 @@ public void analyzeEvents() { String eventThread = struct.getValue("eventThread").getJavaName(); String notifThread = struct.getValue("notifier") != null ? struct.getValue("notifier").getJavaName() : null; assertTrue("No event thread",eventThread != null); - if (!eventThread.equals(producerName) && !eventThread.equals(consumerName) ) { + if ( + (!eventThread.equals(producerName) && !eventThread.equals(consumerName)) + || !event.getEventType().getName().equals("jdk.JavaMonitorWait") + || !struct.getValue("monitorClass").getName().equals(Helper.class.getName() )) { continue; } - assertTrue( "Wrong event type", event.getEventType().getName().equals("jdk.JavaMonitorWait")); - assertTrue("Wrong event duration", isEqualDuration(Duration.ofMillis(MILLIS), event.getDuration())); - assertFalse("Wrong monitor class.", - !struct.getValue("monitorClass").getName().equals(Helper.class.getName()) - && (eventThread.equals(consumerName) ||eventThread.equals(producerName))); + assertTrue("Wrong event duration", isEqualDuration(Duration.ofMillis(MILLIS), event.getDuration())); assertFalse("Should not have timed out.", struct.getValue("timedOut").booleanValue()); if (lastEventThreadName == null) { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java new file mode 100644 index 000000000000..d4e440f2256f --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2022, Red Hat Inc. 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 com.oracle.svm.test.jfr; + +import static java.lang.Math.abs; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.time.Duration; +import java.util.List; + +import org.junit.Test; + +import com.oracle.svm.test.jfr.JfrTest; + +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedObject; +import jdk.jfr.consumer.RecordedThread; + +public class TestJavaMonitorWaitTimeout extends JfrTest { + private static final int MILLIS = 50; + static Helper helper = new Helper(); + static String timeOutName; + static String notifierName; + static String simpleWaitName; + static String simpleNotifyName; + + @Override + public String[] getTestedEvents() { + return new String[]{"jdk.JavaMonitorWait"}; + } + @Override + public void analyzeEvents() { + List events; + try { + events = getEvents(recording, "jdk.JavaMonitorWait"); + } catch (IOException e) { + throw new RuntimeException(e); + } + for (RecordedEvent event : events) { + RecordedObject struct = event; + if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) { + continue; + } + String eventThread = struct.getValue("eventThread").getJavaName(); + String notifThread = struct.getValue("notifier") != null ? struct.getValue("notifier").getJavaName() : null; + if (!eventThread.equals(notifierName) && + !eventThread.equals(timeOutName) && + !eventThread.equals(simpleNotifyName) && + !eventThread.equals(simpleWaitName)) { + continue; + } + if (!struct.getValue("monitorClass").getName().equals(Helper.class.getName())) { + continue; + } + assertTrue("Event is wrong duration.", isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration())); + if (eventThread.equals(timeOutName)) { + assertTrue("Notifier of timeout thread should be null", notifThread == null); + assertTrue("Should have timed out.", struct.getValue("timedOut").booleanValue()); + } else if (eventThread.equals(simpleWaitName)) { + assertTrue("Notifier of simple wait is incorrect", notifThread.equals(simpleNotifyName)); + } + + } + } + + + @Test + public void test() throws Exception { + Runnable unheardNotifier = () -> { + try { + helper.unheardNotify(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + Runnable timouter = () -> { + try { + helper.timeout(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + Runnable simpleWaiter = () -> { + try { + helper.simpleWait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + Runnable simpleNotifier = () -> { + try { + helper.simpleNotify(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + Thread unheardNotifierThread = new Thread(unheardNotifier); + Thread timeoutThread = new Thread(timouter); + timeOutName = unheardNotifierThread.getName(); + notifierName = unheardNotifierThread.getName(); + + + timeoutThread.start(); + Thread.sleep(10); + unheardNotifierThread.start(); + + timeoutThread.join(); + unheardNotifierThread.join(); + + Thread tw = new Thread(simpleWaiter); + Thread tn = new Thread(simpleNotifier); + simpleWaitName = tw.getName(); + simpleNotifyName = tn.getName(); + + + tw.start(); + Thread.sleep(10); + tn.start(); + + tw.join(); + tn.join(); + + // sleep so we know the event is recorded + Thread.sleep(500); + } + + static class Helper { + public synchronized void timeout() throws InterruptedException { + wait(MILLIS); + } + + public synchronized void unheardNotify() throws InterruptedException { + Thread.sleep(2*MILLIS); + //notify after timeout + notify(); + } + + public synchronized void simpleWait() throws InterruptedException { + wait(); + } + public synchronized void simpleNotify() throws InterruptedException { + Thread.sleep(2*MILLIS); + notify(); + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java index aed5475b2ae5..770ef96b23a8 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java @@ -56,15 +56,17 @@ public void analyzeEvents() { } boolean foundSleepEvent = false; for (RecordedEvent event : events) { + if (!event.getEventType().getName().equals("jdk.ThreadSleep")) { + continue; + } RecordedObject struct = event; String eventThread = struct.getValue("eventThread").getJavaName(); if (!eventThread.equals(sleepingThreadName)) { continue; } - assertTrue("wrong event type",event.getEventType().getName().equals("jdk.ThreadSleep")); - - assertTrue("Slept wrong duration.",isEqualDuration(event.getDuration(), Duration.ofMillis(MILLIS))); - foundSleepEvent = true; + if (!isEqualDuration(event.getDuration(), Duration.ofMillis(MILLIS))) { + continue; + } foundSleepEvent = true; break; } assertTrue("Sleep event not found.", foundSleepEvent); From 06a3c9ffac78893633d3c51ef35e4e53d2231409 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 12 Sep 2022 15:07:47 -0400 Subject: [PATCH 04/21] add monitor wait interrupt test. checkstyle, format --- .../src/com/oracle/svm/test/jfr/JfrTest.java | 22 ++- .../src/com/oracle/svm/test/jfr/Stressor.java | 2 +- .../svm/test/jfr/TestJavaMonitorEnter.java | 27 ++- .../svm/test/jfr/TestJavaMonitorWait.java | 28 ++- .../jfr/TestJavaMonitorWaitInterrupt.java | 181 ++++++++++++++++++ .../jfr/TestJavaMonitorWaitNotifyAll.java | 143 ++++++++++++++ .../test/jfr/TestJavaMonitorWaitTimeout.java | 39 ++-- .../oracle/svm/test/jfr/TestThreadSleep.java | 8 +- 8 files changed, 386 insertions(+), 64 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java index d776f6ba91d7..b70f6bf30d0f 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java @@ -30,7 +30,6 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.util.Collections; @@ -60,7 +59,7 @@ public abstract class JfrTest { protected Jfr jfr; protected Recording recording; private ChronologicalComparator chronologicalComparator = new ChronologicalComparator(); - protected final long MS_TOLERANCE = 10; + protected final long msTolerance = 10; @BeforeClass public static void checkForJFR() { @@ -111,7 +110,8 @@ protected void enableEvents(String[] events) { } public abstract String[] getTestedEvents(); - public void analyzeEvents(){ + + public void analyzeEvents() { } protected void checkEvents() { @@ -147,15 +147,17 @@ public int compare(RecordedEvent e1, RecordedEvent e2) { return e1.getEndTime().compareTo(e2.getEndTime()); } } + private Path makeCopy(Recording recording, String testName) throws IOException { // from jdk 19 Path p = recording.getDestination(); if (p == null) { File directory = new File("."); - p = new File(directory.getAbsolutePath(), "recording-" + recording.getId() + "-" + testName+ ".jfr").toPath(); + p = new File(directory.getAbsolutePath(), "recording-" + recording.getId() + "-" + testName + ".jfr").toPath(); recording.dump(p); } return p; } + protected List getEvents(Recording recording, String testName) throws IOException { Path p = makeCopy(recording, testName); List events = RecordingFile.readAllEvents(p); @@ -163,15 +165,17 @@ protected List getEvents(Recording recording, String testName) th return events; } - - /** Used for comparing durations with a tolerance of MS_TOLERANCE */ + /** Used for comparing durations with a tolerance of MS_TOLERANCE. */ protected boolean isEqualDuration(Duration d1, Duration d2) { - return d1.minus(d2).abs().compareTo(Duration.ofMillis(MS_TOLERANCE)) < 0; + return d1.minus(d2).abs().compareTo(Duration.ofMillis(msTolerance)) < 0; } - /** Used for comparing durations with a tolerance of MS_TOLERANCE. True if 'larger' really is bigger */ + /** + * Used for comparing durations with a tolerance of MS_TOLERANCE. True if 'larger' really is + * bigger + */ protected boolean isGreaterDuration(Duration smaller, Duration larger) { - return smaller.minus(larger.plus(Duration.ofMillis(MS_TOLERANCE))).isNegative(); + return smaller.minus(larger.plus(Duration.ofMillis(msTolerance))).isNegative(); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java index 29121ce12e56..88ae9316c211 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java @@ -29,7 +29,7 @@ import java.util.List; /** - * Class to help run multiple threads executing some task + * Class to help run multiple threads executing some task. */ public class Stressor { public static void execute(int numberOfThreads, Thread.UncaughtExceptionHandler eh, Runnable task) throws Exception { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java index 0e4f02f197f4..17aad6eaa956 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java @@ -27,12 +27,10 @@ package com.oracle.svm.test.jfr; import static java.lang.Math.abs; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.time.Duration; -import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue; @@ -42,20 +40,19 @@ import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedObject; -import com.oracle.svm.test.jfr.JfrTest; - -import com.oracle.svm.test.jfr.Stressor; public class TestJavaMonitorEnter extends JfrTest { - private final int THREADS = 10; + private final int threads = 10; private static final int MILLIS = 60; static Object monitor = new Object(); private static Queue orderedWaiterNames = new LinkedList<>(); + @Override public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorEnter"}; } + @Override public void analyzeEvents() { List events; @@ -65,34 +62,34 @@ public void analyzeEvents() { throw new RuntimeException(e); } int count = 0; - orderedWaiterNames.poll(); //first worker does not wait + orderedWaiterNames.poll(); // first worker does not wait String waiterName = orderedWaiterNames.poll(); Long prev = 0L; for (RecordedEvent event : events) { RecordedObject struct = event; - String eventThread = struct.getValue("eventThread").getJavaName(); - if (event.getEventType().getName().equals("jdk.JavaMonitorEnter") - && isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration()) - && waiterName.equals(eventThread)) { + String eventThread = struct. getValue("eventThread").getJavaName(); + if (event.getEventType().getName().equals("jdk.JavaMonitorEnter") && isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration()) && waiterName.equals(eventThread)) { Long duration = event.getDuration().toMillis(); - assertTrue( "Durations not as expected ", abs(duration - prev - MILLIS) < MS_TOLERANCE ); + assertTrue("Durations not as expected ", abs(duration - prev - MILLIS) < msTolerance); count++; waiterName = orderedWaiterNames.poll(); prev = duration; } } - assertTrue("Wrong number of Java Monitor Enter Events " + count, count == THREADS - 1);// -1 because first thread does not get blocked by any previous thread + assertTrue("Wrong number of Java Monitor Enter Events " + count, count == threads - 1); + } private static void doWork(Object obj) throws InterruptedException { - synchronized(obj){ + synchronized (obj) { Thread.sleep(MILLIS); orderedWaiterNames.add(Thread.currentThread().getName()); } } + @Test public void test() throws Exception { - int threadCount = THREADS; + int threadCount = threads; Runnable r = () -> { // create contention between threads for one lock try { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java index 5ba28776182c..1e8fe39e5525 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java @@ -37,8 +37,6 @@ import jdk.jfr.consumer.RecordedClass; import org.junit.Test; -import com.oracle.svm.test.jfr.JfrTest; - import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedObject; import jdk.jfr.consumer.RecordedThread; @@ -54,6 +52,7 @@ public class TestJavaMonitorWait extends JfrTest { public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; } + @Override public void analyzeEvents() { List events; @@ -65,21 +64,19 @@ public void analyzeEvents() { int prodCount = 0; int consCount = 0; - String lastEventThreadName = null; //should alternate if buffer is 1 + String lastEventThreadName = null; // should alternate if buffer is 1 for (RecordedEvent event : events) { RecordedObject struct = event; - String eventThread = struct.getValue("eventThread").getJavaName(); - String notifThread = struct.getValue("notifier") != null ? struct.getValue("notifier").getJavaName() : null; - assertTrue("No event thread",eventThread != null); - if ( - (!eventThread.equals(producerName) && !eventThread.equals(consumerName)) - || !event.getEventType().getName().equals("jdk.JavaMonitorWait") - || !struct.getValue("monitorClass").getName().equals(Helper.class.getName() )) { + String eventThread = struct. getValue("eventThread").getJavaName(); + String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; + assertTrue("No event thread", eventThread != null); + if ((!eventThread.equals(producerName) && !eventThread.equals(consumerName)) || !event.getEventType().getName().equals("jdk.JavaMonitorWait") || + !struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { continue; } assertTrue("Wrong event duration", isEqualDuration(Duration.ofMillis(MILLIS), event.getDuration())); - assertFalse("Should not have timed out.", struct.getValue("timedOut").booleanValue()); + assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue()); if (lastEventThreadName == null) { lastEventThreadName = notifThread; @@ -94,11 +91,10 @@ public void analyzeEvents() { } lastEventThreadName = eventThread; } - assertFalse("Wrong number of events: "+prodCount + " "+consCount, - abs(prodCount - consCount) > 1 || abs(consCount-COUNT) >1); + assertFalse("Wrong number of events: " + prodCount + " " + consCount, + abs(prodCount - consCount) > 1 || abs(consCount - COUNT) > 1); } - @Test public void test() throws Exception { Runnable consumer = () -> { @@ -134,7 +130,7 @@ static class Helper { private final int bufferSize = 1; public synchronized void produce() throws InterruptedException { - for (int i = 0; i< COUNT; i++) { + for (int i = 0; i < COUNT; i++) { while (count >= bufferSize) { wait(); } @@ -145,7 +141,7 @@ public synchronized void produce() throws InterruptedException { } public synchronized void consume() throws InterruptedException { - for (int i = 0; i< COUNT; i++) { + for (int i = 0; i < COUNT; i++) { while (count == 0) { wait(); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java new file mode 100644 index 000000000000..de634ce1c41d --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2022, Red Hat Inc. 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 com.oracle.svm.test.jfr; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.time.Duration; +import java.util.List; + +import org.junit.Test; + +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedObject; +import jdk.jfr.consumer.RecordedThread; + +public class TestJavaMonitorWaitInterrupt extends JfrTest { + private static final int MILLIS = 50; + static Helper helper = new Helper(); + static String interruptedName; + static String interrupterName; + static String simpleWaitName; + static String simpleNotifyName; + + private boolean interruptedFound = false; + private boolean simpleWaitFound = false; + + @Override + public String[] getTestedEvents() { + return new String[]{"jdk.JavaMonitorWait"}; + } + + @Override + public void analyzeEvents() { + List events; + try { + events = getEvents(recording, "TestJavaMonitorWaitInterrupt"); + } catch (IOException e) { + throw new RuntimeException(e); + } + for (RecordedEvent event : events) { + RecordedObject struct = event; + if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) { + continue; + } + String eventThread = struct. getValue("eventThread").getJavaName(); + String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; + if (!eventThread.equals(interrupterName) && + !eventThread.equals(interruptedName) && + !eventThread.equals(simpleNotifyName) && + !eventThread.equals(simpleWaitName)) { + continue; + } + if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { + continue; + } + assertTrue("Event is wrong duration.", isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration())); + assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue()); + + if (eventThread.equals(interruptedName)) { + assertTrue("Notifier of interrupted thread should be null", notifThread == null); + interruptedFound = true; + } else if (eventThread.equals(simpleWaitName)) { + assertTrue("Notifier of simple wait is incorrect: " + notifThread + " " + simpleNotifyName, notifThread.equals(simpleNotifyName)); + simpleWaitFound = true; + } + } + assertTrue("Couldn't find expected wait events. SimpleWaiter: " + simpleWaitFound + " interrupted: " + interruptedFound, + simpleWaitFound && interruptedFound); + } + + @Test + public void test() throws Exception { + Runnable interrupter = () -> { + try { + helper.interrupt(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + Runnable interrupted = () -> { + try { + helper.interrupted(); + throw new RuntimeException("Was not interrupted!!"); + } catch (InterruptedException e) { + // should get interrupted + } + }; + + Runnable simpleWaiter = () -> { + try { + helper.simpleWait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + Runnable simpleNotifier = () -> { + try { + helper.simpleNotify(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + Thread interrupterThread = new Thread(interrupter); + Thread interruptedThread = new Thread(interrupted); + helper.interrupted = interruptedThread; + interrupterName = interrupterThread.getName(); + interruptedName = interruptedThread.getName(); + + interruptedThread.start(); + Thread.sleep(MILLIS); // pause to ensure expected ordering of lock acquisition + interrupterThread.start(); + + interruptedThread.join(); + interrupterThread.join(); + + Thread tw = new Thread(simpleWaiter); + Thread tn = new Thread(simpleNotifier); + simpleWaitName = tw.getName(); + simpleNotifyName = tn.getName(); + + tw.start(); + Thread.sleep(50); + tn.start(); + + tw.join(); + tn.join(); + + // sleep so we know the event is recorded + Thread.sleep(500); + } + + static class Helper { + public Thread interrupted; + + public synchronized void interrupted() throws InterruptedException { + wait(); + } + + public synchronized void interrupt() throws InterruptedException { + interrupted.interrupt(); + } + + public synchronized void simpleWait() throws InterruptedException { + wait(); + } + + public synchronized void simpleNotify() throws InterruptedException { + Thread.sleep(2 * MILLIS); + notify(); + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java new file mode 100644 index 000000000000..46d0f988127e --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2022, Red Hat Inc. 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 com.oracle.svm.test.jfr; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.time.Duration; +import java.util.List; + +import org.junit.Test; + +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedObject; +import jdk.jfr.consumer.RecordedThread; + +public class TestJavaMonitorWaitNotifyAll extends JfrTest { + private static final int MILLIS = 50; + static Helper helper = new Helper(); + static String waiterName1; + static String waiterName2; + static String notifierName; + private boolean notifierFound = false; + private int waitersFound = 0; + + @Override + public String[] getTestedEvents() { + return new String[]{"jdk.JavaMonitorWait"}; + } + + @Override + public void analyzeEvents() { + List events; + try { + events = getEvents(recording, "TestJavaMonitorWaitNotifyAll"); + } catch (IOException e) { + throw new RuntimeException(e); + } + for (RecordedEvent event : events) { + RecordedObject struct = event; + if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) { + continue; + } + String eventThread = struct. getValue("eventThread").getJavaName(); + String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; + if (!eventThread.equals(waiterName1) && + !eventThread.equals(waiterName2) && + !eventThread.equals(notifierName)) { + continue; + } + if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { + continue; + } + + assertTrue("Event is wrong duration.", isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration())); + + if (eventThread.equals(notifierName)) { + assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue()); + notifierFound = true; + } else { + assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue()); + assertTrue("Notifier thread name is incorrect", notifThread.equals(notifierName)); + waitersFound++; + } + } + assertTrue("Couldn't find expected wait events. NotifierFound: " + notifierFound + " waitersFound: " + waitersFound, + notifierFound && waitersFound == 2); + } + + @Test + public void test() throws Exception { + Runnable consumer = () -> { + try { + helper.consume(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + Runnable producer = () -> { + try { + helper.produce(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + Thread tc1 = new Thread(consumer); + Thread tp1 = new Thread(producer); + Thread tp2 = new Thread(producer); + waiterName1 = tp1.getName(); + waiterName2 = tp2.getName(); + notifierName = tc1.getName(); + + tp1.start(); + tp2.start(); + tc1.start(); + + tp1.join(); + tp2.join(); + tc1.join(); + + // sleep so we know the event is recorded + Thread.sleep(500); + } + + static class Helper { + public synchronized void produce() throws InterruptedException { + wait(); + } + + public synchronized void consume() throws InterruptedException { + // give the producers a headstart so they can start waiting + wait(MILLIS); + notifyAll(); // should wake up both producers + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java index d4e440f2256f..2c900e3ebf41 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java @@ -26,8 +26,6 @@ package com.oracle.svm.test.jfr; -import static java.lang.Math.abs; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.IOException; @@ -36,8 +34,6 @@ import org.junit.Test; -import com.oracle.svm.test.jfr.JfrTest; - import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedObject; @@ -50,16 +46,19 @@ public class TestJavaMonitorWaitTimeout extends JfrTest { static String notifierName; static String simpleWaitName; static String simpleNotifyName; + private boolean timeoutFound = false; + private boolean simpleWaitFound = false; @Override public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; } + @Override public void analyzeEvents() { List events; try { - events = getEvents(recording, "jdk.JavaMonitorWait"); + events = getEvents(recording, "TestJavaMonitorWaitTimeout"); } catch (IOException e) { throw new RuntimeException(e); } @@ -68,29 +67,32 @@ public void analyzeEvents() { if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) { continue; } - String eventThread = struct.getValue("eventThread").getJavaName(); - String notifThread = struct.getValue("notifier") != null ? struct.getValue("notifier").getJavaName() : null; + String eventThread = struct. getValue("eventThread").getJavaName(); + String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; if (!eventThread.equals(notifierName) && - !eventThread.equals(timeOutName) && - !eventThread.equals(simpleNotifyName) && - !eventThread.equals(simpleWaitName)) { + !eventThread.equals(timeOutName) && + !eventThread.equals(simpleNotifyName) && + !eventThread.equals(simpleWaitName)) { continue; } - if (!struct.getValue("monitorClass").getName().equals(Helper.class.getName())) { + if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { continue; } assertTrue("Event is wrong duration.", isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration())); if (eventThread.equals(timeOutName)) { assertTrue("Notifier of timeout thread should be null", notifThread == null); - assertTrue("Should have timed out.", struct.getValue("timedOut").booleanValue()); + assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue()); + timeoutFound = true; } else if (eventThread.equals(simpleWaitName)) { assertTrue("Notifier of simple wait is incorrect", notifThread.equals(simpleNotifyName)); + simpleWaitFound = true; } } + assertTrue("Couldn't find expected wait events. SimpleWaiter: " + simpleWaitFound + " timeout: " + timeoutFound, + simpleWaitFound && timeoutFound); } - @Test public void test() throws Exception { Runnable unheardNotifier = () -> { @@ -126,10 +128,9 @@ public void test() throws Exception { }; Thread unheardNotifierThread = new Thread(unheardNotifier); Thread timeoutThread = new Thread(timouter); - timeOutName = unheardNotifierThread.getName(); + timeOutName = timeoutThread.getName(); notifierName = unheardNotifierThread.getName(); - timeoutThread.start(); Thread.sleep(10); unheardNotifierThread.start(); @@ -142,7 +143,6 @@ public void test() throws Exception { simpleWaitName = tw.getName(); simpleNotifyName = tn.getName(); - tw.start(); Thread.sleep(10); tn.start(); @@ -160,16 +160,17 @@ public synchronized void timeout() throws InterruptedException { } public synchronized void unheardNotify() throws InterruptedException { - Thread.sleep(2*MILLIS); - //notify after timeout + Thread.sleep(2 * MILLIS); + // notify after timeout notify(); } public synchronized void simpleWait() throws InterruptedException { wait(); } + public synchronized void simpleNotify() throws InterruptedException { - Thread.sleep(2*MILLIS); + Thread.sleep(2 * MILLIS); notify(); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java index 770ef96b23a8..0ad7ca03f46c 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java @@ -36,7 +36,6 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; -import com.oracle.svm.test.jfr.JfrTest; public class TestThreadSleep extends JfrTest { private String sleepingThreadName; @@ -46,6 +45,7 @@ public class TestThreadSleep extends JfrTest { public String[] getTestedEvents() { return new String[]{"jdk.ThreadSleep"}; } + @Override public void analyzeEvents() { List events; @@ -60,19 +60,19 @@ public void analyzeEvents() { continue; } RecordedObject struct = event; - String eventThread = struct.getValue("eventThread").getJavaName(); + String eventThread = struct. getValue("eventThread").getJavaName(); if (!eventThread.equals(sleepingThreadName)) { continue; } if (!isEqualDuration(event.getDuration(), Duration.ofMillis(MILLIS))) { continue; - } foundSleepEvent = true; + } + foundSleepEvent = true; break; } assertTrue("Sleep event not found.", foundSleepEvent); } - @Test public void test() throws Exception { sleepingThreadName = Thread.currentThread().getName(); From c7b548654de2b679cfda054a81705cfc9a803485 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 12 Sep 2022 15:50:44 -0400 Subject: [PATCH 05/21] fix hiding field and exceptions --- .../src/com/oracle/svm/test/jfr/JfrTest.java | 6 ++--- .../svm/test/jfr/TestJavaMonitorEnter.java | 2 +- .../svm/test/jfr/TestJavaMonitorWait.java | 2 +- .../jfr/TestJavaMonitorWaitInterrupt.java | 27 ++++++++++++++----- .../jfr/TestJavaMonitorWaitNotifyAll.java | 2 +- .../test/jfr/TestJavaMonitorWaitTimeout.java | 2 +- .../oracle/svm/test/jfr/TestThreadSleep.java | 2 +- 7 files changed, 29 insertions(+), 14 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java index b70f6bf30d0f..4ced427f431a 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java @@ -148,7 +148,7 @@ public int compare(RecordedEvent e1, RecordedEvent e2) { } } - private Path makeCopy(Recording recording, String testName) throws IOException { // from jdk 19 + private Path makeCopy(String testName) throws IOException { // from jdk 19 Path p = recording.getDestination(); if (p == null) { File directory = new File("."); @@ -158,8 +158,8 @@ private Path makeCopy(Recording recording, String testName) throws IOException { return p; } - protected List getEvents(Recording recording, String testName) throws IOException { - Path p = makeCopy(recording, testName); + protected List getEvents(String testName) throws IOException { + Path p = makeCopy(testName); List events = RecordingFile.readAllEvents(p); Collections.sort(events, chronologicalComparator); return events; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java index 17aad6eaa956..1d6de4d1029d 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java @@ -57,7 +57,7 @@ public String[] getTestedEvents() { public void analyzeEvents() { List events; try { - events = getEvents(recording, "jdk.JavaMonitorEnter"); + events = getEvents("jdk.JavaMonitorEnter"); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java index 1e8fe39e5525..8bbffc0d3323 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java @@ -57,7 +57,7 @@ public String[] getTestedEvents() { public void analyzeEvents() { List events; try { - events = getEvents(recording, "jdk.JavaMonitorWait"); + events = getEvents("jdk.JavaMonitorWait"); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java index de634ce1c41d..9d98fe855340 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java @@ -60,7 +60,7 @@ public String[] getTestedEvents() { public void analyzeEvents() { List events; try { - events = getEvents(recording, "TestJavaMonitorWaitInterrupt"); + events = getEvents("TestJavaMonitorWaitInterrupt"); } catch (IOException e) { throw new RuntimeException(e); } @@ -162,20 +162,35 @@ static class Helper { public Thread interrupted; public synchronized void interrupted() throws InterruptedException { - wait(); + try { + wait(); + } catch (InterruptedException e) { + throw new InterruptedException("expected interrupt"); + } } public synchronized void interrupt() throws InterruptedException { - interrupted.interrupt(); + try { + interrupted.interrupt(); + } catch (Exception e) { + } + } public synchronized void simpleWait() throws InterruptedException { - wait(); + try { + wait(); + } catch (Exception e) { + + } } public synchronized void simpleNotify() throws InterruptedException { - Thread.sleep(2 * MILLIS); - notify(); + try { + Thread.sleep(2 * MILLIS); + notify(); + } catch (Exception e) { + } } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java index 46d0f988127e..3a9c933c3419 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java @@ -58,7 +58,7 @@ public String[] getTestedEvents() { public void analyzeEvents() { List events; try { - events = getEvents(recording, "TestJavaMonitorWaitNotifyAll"); + events = getEvents("TestJavaMonitorWaitNotifyAll"); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java index 2c900e3ebf41..b2dc3deec0e2 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java @@ -58,7 +58,7 @@ public String[] getTestedEvents() { public void analyzeEvents() { List events; try { - events = getEvents(recording, "TestJavaMonitorWaitTimeout"); + events = getEvents("TestJavaMonitorWaitTimeout"); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java index 0ad7ca03f46c..3ec1535e8e41 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java @@ -50,7 +50,7 @@ public String[] getTestedEvents() { public void analyzeEvents() { List events; try { - events = getEvents(recording, "jdk.ThreadSleep"); + events = getEvents("jdk.ThreadSleep"); } catch (IOException e) { throw new RuntimeException(e); } From 5bdeff652b0902fa50cbd95284ad8b9a99eaa5bc Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 12 Sep 2022 16:26:28 -0400 Subject: [PATCH 06/21] remove unused try catch blocks --- .../jfr/TestJavaMonitorWaitInterrupt.java | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java index 9d98fe855340..55c189c14603 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java @@ -98,11 +98,7 @@ public void analyzeEvents() { @Test public void test() throws Exception { Runnable interrupter = () -> { - try { - helper.interrupt(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + helper.interrupt(); }; Runnable interrupted = () -> { @@ -115,19 +111,11 @@ public void test() throws Exception { }; Runnable simpleWaiter = () -> { - try { - helper.simpleWait(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + helper.simpleWait(); }; Runnable simpleNotifier = () -> { - try { - helper.simpleNotify(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + helper.simpleNotify(); }; Thread interrupterThread = new Thread(interrupter); Thread interruptedThread = new Thread(interrupted); @@ -169,7 +157,7 @@ public synchronized void interrupted() throws InterruptedException { } } - public synchronized void interrupt() throws InterruptedException { + public synchronized void interrupt() { try { interrupted.interrupt(); } catch (Exception e) { @@ -177,7 +165,7 @@ public synchronized void interrupt() throws InterruptedException { } - public synchronized void simpleWait() throws InterruptedException { + public synchronized void simpleWait() { try { wait(); } catch (Exception e) { @@ -185,7 +173,7 @@ public synchronized void simpleWait() throws InterruptedException { } } - public synchronized void simpleNotify() throws InterruptedException { + public synchronized void simpleNotify() { try { Thread.sleep(2 * MILLIS); notify(); From 727adae18a313493f034e713e0efd230d90bcda8 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 7 Oct 2022 14:11:20 -0400 Subject: [PATCH 07/21] use spinlocks for synchronization. Refactor. --- .../src/com/oracle/svm/test/jfr/JfrTest.java | 25 ++-- .../src/com/oracle/svm/test/jfr/Stressor.java | 3 +- .../svm/test/jfr/TestJavaMonitorEnter.java | 97 +++++++------ .../svm/test/jfr/TestJavaMonitorWait.java | 16 +-- .../jfr/TestJavaMonitorWaitInterrupt.java | 129 +++++++++--------- .../jfr/TestJavaMonitorWaitNotifyAll.java | 85 ++++++------ .../test/jfr/TestJavaMonitorWaitTimeout.java | 121 ++++++++-------- .../oracle/svm/test/jfr/TestThreadSleep.java | 5 +- 8 files changed, 234 insertions(+), 247 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java index 4ced427f431a..5963f7a0a4a2 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java @@ -58,7 +58,7 @@ public abstract class JfrTest { protected Jfr jfr; protected Recording recording; - private ChronologicalComparator chronologicalComparator = new ChronologicalComparator(); + private final ChronologicalComparator chronologicalComparator = new ChronologicalComparator(); protected final long msTolerance = 10; @BeforeClass @@ -85,11 +85,15 @@ public void startRecording() { public void endRecording() { try { jfr.endRecording(recording); - analyzeEvents(); } catch (Exception e) { Assert.fail("Fail to stop recording! Cause: " + e.getMessage()); } - + checkEvents(); + try { + validateEvents(); + }catch (Throwable throwable) { + Assert.fail("validateEvents failed: " + throwable.getMessage()); + } try { checkRecording(); } finally { @@ -111,7 +115,7 @@ protected void enableEvents(String[] events) { public abstract String[] getTestedEvents(); - public void analyzeEvents() { + public void validateEvents() throws Throwable{ } protected void checkEvents() { @@ -165,19 +169,6 @@ protected List getEvents(String testName) throws IOException { return events; } - /** Used for comparing durations with a tolerance of MS_TOLERANCE. */ - protected boolean isEqualDuration(Duration d1, Duration d2) { - return d1.minus(d2).abs().compareTo(Duration.ofMillis(msTolerance)) < 0; - } - - /** - * Used for comparing durations with a tolerance of MS_TOLERANCE. True if 'larger' really is - * bigger - */ - protected boolean isGreaterDuration(Duration smaller, Duration larger) { - return smaller.minus(larger.plus(Duration.ofMillis(msTolerance))).isNegative(); - } - } class JFRTestFeature implements Feature { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java index 88ae9316c211..8667633738ea 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java @@ -32,11 +32,10 @@ * Class to help run multiple threads executing some task. */ public class Stressor { - public static void execute(int numberOfThreads, Thread.UncaughtExceptionHandler eh, Runnable task) throws Exception { + public static void execute(int numberOfThreads, Runnable task) throws Exception { List threads = new ArrayList<>(); for (int n = 0; n < numberOfThreads; ++n) { Thread t = new Thread(task); - t.setUncaughtExceptionHandler(eh); threads.add(t); t.start(); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java index 1d6de4d1029d..40dd2fabfb27 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java @@ -26,15 +26,13 @@ package com.oracle.svm.test.jfr; -import static java.lang.Math.abs; import static org.junit.Assert.assertTrue; -import java.io.IOException; -import java.time.Duration; import java.util.LinkedList; import java.util.List; import java.util.Queue; +import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedThread; import org.junit.Test; @@ -42,10 +40,12 @@ import jdk.jfr.consumer.RecordedObject; public class TestJavaMonitorEnter extends JfrTest { - private final int threads = 10; private static final int MILLIS = 60; - static Object monitor = new Object(); + static boolean inCritical = false; + static Thread firstThread; + static Thread secondThread; + static final Helper helper = new Helper(); private static Queue orderedWaiterNames = new LinkedList<>(); @Override @@ -53,60 +53,75 @@ public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorEnter"}; } - @Override - public void analyzeEvents() { + public void validateEvents() throws Throwable{ List events; - try { - events = getEvents("jdk.JavaMonitorEnter"); - } catch (IOException e) { - throw new RuntimeException(e); - } + events = getEvents("jdk.JavaMonitorEnter"); int count = 0; - orderedWaiterNames.poll(); // first worker does not wait - String waiterName = orderedWaiterNames.poll(); - Long prev = 0L; + boolean found = false; for (RecordedEvent event : events) { RecordedObject struct = event; String eventThread = struct. getValue("eventThread").getJavaName(); - if (event.getEventType().getName().equals("jdk.JavaMonitorEnter") && isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration()) && waiterName.equals(eventThread)) { - Long duration = event.getDuration().toMillis(); - assertTrue("Durations not as expected ", abs(duration - prev - MILLIS) < msTolerance); - count++; - waiterName = orderedWaiterNames.poll(); - prev = duration; + if (event.getEventType().getName().equals("jdk.JavaMonitorEnter") + && struct. getValue("monitorClass").getName().equals(Helper.class.getName()) + && event.getDuration().toMillis() >= MILLIS + && secondThread.getName().equals(eventThread)) { + + // verify previous owner + assertTrue("Previous owner is wrong",struct. getValue("previousOwner").getJavaName().equals(firstThread.getName())); + found = true; + break; } } - assertTrue("Wrong number of Java Monitor Enter Events " + count, count == threads - 1); + assertTrue("Expected monitor blocked event not found" , found); } - private static void doWork(Object obj) throws InterruptedException { - synchronized (obj) { - Thread.sleep(MILLIS); - orderedWaiterNames.add(Thread.currentThread().getName()); - } - } - @Test public void test() throws Exception { - int threadCount = threads; - Runnable r = () -> { - // create contention between threads for one lock + Runnable first = () -> { try { - doWork(monitor); + helper.doWork(); } catch (InterruptedException e) { throw new RuntimeException(e); } + }; + Runnable second = () -> { + try { + //wait until lock is held + while(!inCritical) { + Thread.sleep(10); + } + helper.doWork(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } }; - Thread.UncaughtExceptionHandler eh = (t, e) -> e.printStackTrace(); - - try { - Stressor.execute(threadCount, eh, r); - // sleep so we know the event is recorded - Thread.sleep(500); - } catch (InterruptedException e) { - throw new RuntimeException(e); + + firstThread = new Thread(first); + secondThread = new Thread(second); + firstThread.start(); + secondThread.start(); + + firstThread.join(); + secondThread.join(); + } + + static class Helper { + private synchronized void doWork() throws InterruptedException { + inCritical = true; + if (Thread.currentThread().equals(secondThread)) { + inCritical = false; + return; // second thread doesn't need to do work. + } + + // spin until second thread blocks + while(!secondThread.getState().equals(Thread.State.BLOCKED)) { + Thread.sleep(10); + } + + Thread.sleep(MILLIS); + inCritical = false; } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java index 8bbffc0d3323..0a5db97f8d0d 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java @@ -46,21 +46,16 @@ public class TestJavaMonitorWait extends JfrTest { private static final int COUNT = 10; private String producerName; private String consumerName; - static Helper helper = new Helper(); + static final Helper helper = new Helper(); @Override public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; } - @Override - public void analyzeEvents() { + public void validateEvents() throws Throwable{ List events; - try { - events = getEvents("jdk.JavaMonitorWait"); - } catch (IOException e) { - throw new RuntimeException(e); - } + events = getEvents("jdk.JavaMonitorWait"); int prodCount = 0; int consCount = 0; @@ -75,7 +70,7 @@ public void analyzeEvents() { continue; } - assertTrue("Wrong event duration", isEqualDuration(Duration.ofMillis(MILLIS), event.getDuration())); + assertTrue("Wrong event duration", event.getDuration().toMillis() >= MILLIS); assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue()); if (lastEventThreadName == null) { @@ -120,9 +115,6 @@ public void test() throws Exception { tc.start(); tp.join(); tc.join(); - - // sleep so we know the event is recorded - Thread.sleep(500); } static class Helper { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java index 55c189c14603..ed53f5379fd9 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java @@ -29,11 +29,10 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.io.IOException; -import java.time.Duration; import java.util.List; import org.junit.Test; +import org.junit.Assert; import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; @@ -42,11 +41,11 @@ public class TestJavaMonitorWaitInterrupt extends JfrTest { private static final int MILLIS = 50; - static Helper helper = new Helper(); - static String interruptedName; - static String interrupterName; - static String simpleWaitName; - static String simpleNotifyName; + static final Helper helper = new Helper(); + static Thread interruptedThread; + static Thread interrupterThread; + static Thread simpleWaitThread; + static Thread simpleNotifyThread; private boolean interruptedFound = false; private boolean simpleWaitFound = false; @@ -56,14 +55,10 @@ public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; } - @Override - public void analyzeEvents() { + public void validateEvents() throws Throwable{ List events; - try { - events = getEvents("TestJavaMonitorWaitInterrupt"); - } catch (IOException e) { - throw new RuntimeException(e); - } + events = getEvents("TestJavaMonitorWaitInterrupt"); + for (RecordedEvent event : events) { RecordedObject struct = event; if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) { @@ -71,23 +66,23 @@ public void analyzeEvents() { } String eventThread = struct. getValue("eventThread").getJavaName(); String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; - if (!eventThread.equals(interrupterName) && - !eventThread.equals(interruptedName) && - !eventThread.equals(simpleNotifyName) && - !eventThread.equals(simpleWaitName)) { + if (!eventThread.equals(interrupterThread.getName()) && + !eventThread.equals(interruptedThread.getName()) && + !eventThread.equals(simpleNotifyThread.getName()) && + !eventThread.equals(simpleWaitThread.getName())) { continue; } if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { continue; } - assertTrue("Event is wrong duration.", isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration())); + assertTrue("Event is wrong duration.", event.getDuration().toMillis() >= MILLIS); assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue()); - if (eventThread.equals(interruptedName)) { + if (eventThread.equals(interruptedThread.getName())) { assertTrue("Notifier of interrupted thread should be null", notifThread == null); interruptedFound = true; - } else if (eventThread.equals(simpleWaitName)) { - assertTrue("Notifier of simple wait is incorrect: " + notifThread + " " + simpleNotifyName, notifThread.equals(simpleNotifyName)); + } else if (eventThread.equals(simpleWaitThread.getName())) { + assertTrue("Notifier of simple wait is incorrect: " + notifThread + " " + simpleNotifyThread.getName(), notifThread.equals(simpleNotifyThread.getName())); simpleWaitFound = true; } } @@ -95,11 +90,7 @@ public void analyzeEvents() { simpleWaitFound && interruptedFound); } - @Test - public void test() throws Exception { - Runnable interrupter = () -> { - helper.interrupt(); - }; + private void testInterruption() throws Exception{ Runnable interrupted = () -> { try { @@ -109,76 +100,84 @@ public void test() throws Exception { // should get interrupted } }; + interruptedThread = new Thread(interrupted); - Runnable simpleWaiter = () -> { - helper.simpleWait(); - }; - - Runnable simpleNotifier = () -> { - helper.simpleNotify(); + Runnable interrupter = () -> { + try { + while (!interruptedThread.getState().equals(Thread.State.WAITING)) { + Thread.sleep(10); + } + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + helper.interrupt(); }; - Thread interrupterThread = new Thread(interrupter); - Thread interruptedThread = new Thread(interrupted); - helper.interrupted = interruptedThread; - interrupterName = interrupterThread.getName(); - interruptedName = interruptedThread.getName(); + interrupterThread = new Thread(interrupter); interruptedThread.start(); - Thread.sleep(MILLIS); // pause to ensure expected ordering of lock acquisition interrupterThread.start(); - interruptedThread.join(); interrupterThread.join(); + } - Thread tw = new Thread(simpleWaiter); - Thread tn = new Thread(simpleNotifier); - simpleWaitName = tw.getName(); - simpleNotifyName = tn.getName(); + private void testWaitNotify() throws Exception{ + Runnable simpleWaiter = () -> { + helper.simpleWait(); + }; - tw.start(); - Thread.sleep(50); - tn.start(); + Runnable simpleNotifier = () -> { + try { + while (!simpleWaitThread.getState().equals(Thread.State.WAITING)) { + Thread.sleep(10); + } + helper.simpleNotify(); + }catch (Exception e) { + Assert.fail(e.getMessage()); + } + }; - tw.join(); - tn.join(); + simpleWaitThread = new Thread(simpleWaiter); + simpleNotifyThread = new Thread(simpleNotifier); - // sleep so we know the event is recorded - Thread.sleep(500); + simpleWaitThread.start(); + simpleNotifyThread.start(); + simpleWaitThread.join(); + simpleNotifyThread.join(); + } + @Test + public void test() throws Exception { + testInterruption(); + System.out.println("*** testInterruption done"); + testWaitNotify(); } static class Helper { public Thread interrupted; public synchronized void interrupted() throws InterruptedException { - try { - wait(); - } catch (InterruptedException e) { - throw new InterruptedException("expected interrupt"); - } + wait(); } public synchronized void interrupt() { try { - interrupted.interrupt(); - } catch (Exception e) { + Thread.sleep(MILLIS); + interruptedThread.interrupt(); + }catch (Exception e) { + Assert.fail(e.getMessage()); } - } public synchronized void simpleWait() { try { wait(); } catch (Exception e) { - + Assert.fail(e.getMessage()); } } - public synchronized void simpleNotify() { - try { - Thread.sleep(2 * MILLIS); + public synchronized void simpleNotify() throws InterruptedException { + Thread.sleep(MILLIS); notify(); - } catch (Exception e) { - } } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java index 3a9c933c3419..5c3777634f0c 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java @@ -42,10 +42,11 @@ public class TestJavaMonitorWaitNotifyAll extends JfrTest { private static final int MILLIS = 50; - static Helper helper = new Helper(); - static String waiterName1; - static String waiterName2; - static String notifierName; + static final Helper helper = new Helper(); + static Thread producerThread1; + static Thread producerThread2; + static Thread consumerThread; + private boolean notifierFound = false; private int waitersFound = 0; @@ -54,40 +55,36 @@ public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; } - @Override - public void analyzeEvents() { - List events; - try { - events = getEvents("TestJavaMonitorWaitNotifyAll"); - } catch (IOException e) { - throw new RuntimeException(e); - } + public void validateEvents() throws Throwable{ + List events = getEvents("TestJavaMonitorWaitNotifyAll"); + + for (RecordedEvent event : events) { RecordedObject struct = event; if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) { continue; } - String eventThread = struct. getValue("eventThread").getJavaName(); - String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; - if (!eventThread.equals(waiterName1) && - !eventThread.equals(waiterName2) && - !eventThread.equals(notifierName)) { + String eventThread = struct.getValue("eventThread").getJavaName(); + String notifThread = struct.getValue("notifier") != null ? struct.getValue("notifier").getJavaName() : null; + if (!eventThread.equals(producerThread1.getName()) && + !eventThread.equals(producerThread2.getName()) && + !eventThread.equals(consumerThread.getName())) { continue; } - if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { + if (!struct.getValue("monitorClass").getName().equals(Helper.class.getName())) { continue; } - assertTrue("Event is wrong duration.", isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration())); - - if (eventThread.equals(notifierName)) { - assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue()); + assertTrue("Event is wrong duration.", event.getDuration().toMillis() >= MILLIS); + if (eventThread.equals(consumerThread.getName())) { + assertTrue("Should have timed out.", struct.getValue("timedOut").booleanValue()); notifierFound = true; } else { - assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue()); - assertTrue("Notifier thread name is incorrect", notifThread.equals(notifierName)); + assertFalse("Should not have timed out.", struct.getValue("timedOut").booleanValue()); + assertTrue("Notifier thread name is incorrect", notifThread.equals(consumerThread.getName())); waitersFound++; } + } assertTrue("Couldn't find expected wait events. NotifierFound: " + notifierFound + " waitersFound: " + waitersFound, notifierFound && waitersFound == 2); @@ -95,38 +92,35 @@ public void analyzeEvents() { @Test public void test() throws Exception { - Runnable consumer = () -> { + Runnable producer = () -> { try { - helper.consume(); + helper.produce(); } catch (InterruptedException e) { throw new RuntimeException(e); } }; - Runnable producer = () -> { + producerThread1 = new Thread(producer); + producerThread2 = new Thread(producer); + Runnable consumer = () -> { try { - helper.produce(); + while (!producerThread1.getState().equals(Thread.State.WAITING) || !producerThread2.getState().equals(Thread.State.WAITING)) { + Thread.sleep(10); + } + helper.consume(); } catch (InterruptedException e) { throw new RuntimeException(e); } }; - Thread tc1 = new Thread(consumer); - Thread tp1 = new Thread(producer); - Thread tp2 = new Thread(producer); - waiterName1 = tp1.getName(); - waiterName2 = tp2.getName(); - notifierName = tc1.getName(); - - tp1.start(); - tp2.start(); - tc1.start(); - - tp1.join(); - tp2.join(); - tc1.join(); - - // sleep so we know the event is recorded - Thread.sleep(500); + + consumerThread = new Thread(consumer); + consumerThread.start(); + producerThread1.start(); + producerThread2.start(); + + consumerThread.join(); + producerThread1.join(); + producerThread2.join(); } static class Helper { @@ -135,7 +129,6 @@ public synchronized void produce() throws InterruptedException { } public synchronized void consume() throws InterruptedException { - // give the producers a headstart so they can start waiting wait(MILLIS); notifyAll(); // should wake up both producers } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java index b2dc3deec0e2..13e00b9d9dd3 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java @@ -28,11 +28,11 @@ import static org.junit.Assert.assertTrue; -import java.io.IOException; -import java.time.Duration; + import java.util.List; import org.junit.Test; +import org.junit.Assert; import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; @@ -41,11 +41,12 @@ public class TestJavaMonitorWaitTimeout extends JfrTest { private static final int MILLIS = 50; - static Helper helper = new Helper(); - static String timeOutName; - static String notifierName; - static String simpleWaitName; - static String simpleNotifyName; + static final Helper helper = new Helper(); + static Thread unheardNotifierThread; + static Thread timeoutThread; + + static Thread simpleWaitThread; + static Thread simpleNotifyThread; private boolean timeoutFound = false; private boolean simpleWaitFound = false; @@ -54,14 +55,10 @@ public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; } - @Override - public void analyzeEvents() { + public void validateEvents() throws Throwable{ List events; - try { - events = getEvents("TestJavaMonitorWaitTimeout"); - } catch (IOException e) { - throw new RuntimeException(e); - } + events = getEvents("TestJavaMonitorWaitTimeout"); + for (RecordedEvent event : events) { RecordedObject struct = event; if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) { @@ -69,22 +66,22 @@ public void analyzeEvents() { } String eventThread = struct. getValue("eventThread").getJavaName(); String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; - if (!eventThread.equals(notifierName) && - !eventThread.equals(timeOutName) && - !eventThread.equals(simpleNotifyName) && - !eventThread.equals(simpleWaitName)) { + if (!eventThread.equals(unheardNotifierThread.getName()) && + !eventThread.equals(timeoutThread.getName()) && + !eventThread.equals(simpleNotifyThread.getName()) && + !eventThread.equals(simpleWaitThread.getName())) { continue; } if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { continue; } - assertTrue("Event is wrong duration.", isGreaterDuration(Duration.ofMillis(MILLIS), event.getDuration())); - if (eventThread.equals(timeOutName)) { + assertTrue("Event is wrong duration:"+event.getDuration().toMillis(), event.getDuration().toMillis() >= MILLIS); + if (eventThread.equals(timeoutThread.getName())) { assertTrue("Notifier of timeout thread should be null", notifThread == null); assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue()); timeoutFound = true; - } else if (eventThread.equals(simpleWaitName)) { - assertTrue("Notifier of simple wait is incorrect", notifThread.equals(simpleNotifyName)); + } else if (eventThread.equals(simpleWaitThread.getName())) { + assertTrue("Notifier of simple wait is incorrect", notifThread.equals(simpleNotifyThread.getName())); simpleWaitFound = true; } @@ -93,13 +90,13 @@ public void analyzeEvents() { simpleWaitFound && timeoutFound); } - @Test - public void test() throws Exception { + + private void testTimeout() throws InterruptedException { Runnable unheardNotifier = () -> { try { helper.unheardNotify(); } catch (InterruptedException e) { - throw new RuntimeException(e); + Assert.fail(e.getMessage()); } }; @@ -107,51 +104,51 @@ public void test() throws Exception { try { helper.timeout(); } catch (InterruptedException e) { - throw new RuntimeException(e); + Assert.fail(e.getMessage()); } }; + unheardNotifierThread = new Thread(unheardNotifier); + timeoutThread = new Thread(timouter); + + timeoutThread.start(); + timeoutThread.join(); + + //wait for timeout before trying to notify + unheardNotifierThread.start(); + unheardNotifierThread.join(); + + } + + private void testWaitNotify() throws Exception{ Runnable simpleWaiter = () -> { - try { helper.simpleWait(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } }; Runnable simpleNotifier = () -> { try { + while (!simpleWaitThread.getState().equals(Thread.State.WAITING)) { + Thread.sleep(10); + } helper.simpleNotify(); } catch (InterruptedException e) { - throw new RuntimeException(e); + Assert.fail(e.getMessage()); } }; - Thread unheardNotifierThread = new Thread(unheardNotifier); - Thread timeoutThread = new Thread(timouter); - timeOutName = timeoutThread.getName(); - notifierName = unheardNotifierThread.getName(); - - timeoutThread.start(); - Thread.sleep(10); - unheardNotifierThread.start(); - - timeoutThread.join(); - unheardNotifierThread.join(); - Thread tw = new Thread(simpleWaiter); - Thread tn = new Thread(simpleNotifier); - simpleWaitName = tw.getName(); - simpleNotifyName = tn.getName(); + simpleWaitThread = new Thread(simpleWaiter); + simpleNotifyThread = new Thread(simpleNotifier); - tw.start(); - Thread.sleep(10); - tn.start(); - - tw.join(); - tn.join(); - - // sleep so we know the event is recorded - Thread.sleep(500); + simpleWaitThread.start(); + simpleNotifyThread.start(); + simpleWaitThread.join(); + simpleNotifyThread.join(); + } + @Test + public void test() throws Exception { + testTimeout(); + System.out.println("timeout test done"); + testWaitNotify(); } static class Helper { @@ -160,18 +157,20 @@ public synchronized void timeout() throws InterruptedException { } public synchronized void unheardNotify() throws InterruptedException { - Thread.sleep(2 * MILLIS); - // notify after timeout notify(); } - public synchronized void simpleWait() throws InterruptedException { - wait(); + public synchronized void simpleWait() { + try { + wait(); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } } public synchronized void simpleNotify() throws InterruptedException { - Thread.sleep(2 * MILLIS); - notify(); + Thread.sleep(MILLIS); + notify(); } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java index 3ec1535e8e41..e0f5a2242405 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java @@ -46,8 +46,7 @@ public String[] getTestedEvents() { return new String[]{"jdk.ThreadSleep"}; } - @Override - public void analyzeEvents() { + public void validateEvents() { List events; try { events = getEvents("jdk.ThreadSleep"); @@ -64,7 +63,7 @@ public void analyzeEvents() { if (!eventThread.equals(sleepingThreadName)) { continue; } - if (!isEqualDuration(event.getDuration(), Duration.ofMillis(MILLIS))) { + if (event.getDuration().toMillis() < MILLIS) { continue; } foundSleepEvent = true; From ed0f1ffadb7b9a53a123ed7f25e0b978af0125c6 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 7 Oct 2022 15:19:53 -0400 Subject: [PATCH 08/21] filter in getEvents and checkstyle --- .../src/com/oracle/svm/test/jfr/JfrTest.java | 11 +++-- .../src/com/oracle/svm/test/jfr/Stressor.java | 46 ------------------- .../svm/test/jfr/TestJavaMonitorEnter.java | 21 +++------ .../svm/test/jfr/TestJavaMonitorWait.java | 6 +-- .../jfr/TestJavaMonitorWaitInterrupt.java | 19 ++++---- .../jfr/TestJavaMonitorWaitNotifyAll.java | 22 ++++----- .../test/jfr/TestJavaMonitorWaitTimeout.java | 21 ++++----- .../oracle/svm/test/jfr/TestThreadSleep.java | 4 -- 8 files changed, 39 insertions(+), 111 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java index 5963f7a0a4a2..ce8f5082075c 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java @@ -31,11 +31,11 @@ import java.io.File; import java.io.IOException; import java.nio.file.Path; -import java.time.Duration; +import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Comparator; import org.graalvm.nativeimage.ImageInfo; import org.graalvm.nativeimage.hosted.Feature; @@ -59,7 +59,6 @@ public abstract class JfrTest { protected Jfr jfr; protected Recording recording; private final ChronologicalComparator chronologicalComparator = new ChronologicalComparator(); - protected final long msTolerance = 10; @BeforeClass public static void checkForJFR() { @@ -91,7 +90,7 @@ public void endRecording() { checkEvents(); try { validateEvents(); - }catch (Throwable throwable) { + } catch (Throwable throwable) { Assert.fail("validateEvents failed: " + throwable.getMessage()); } try { @@ -115,7 +114,7 @@ protected void enableEvents(String[] events) { public abstract String[] getTestedEvents(); - public void validateEvents() throws Throwable{ + public void validateEvents() throws Throwable { } protected void checkEvents() { @@ -166,6 +165,8 @@ protected List getEvents(String testName) throws IOException { Path p = makeCopy(testName); List events = RecordingFile.readAllEvents(p); Collections.sort(events, chronologicalComparator); + // remove events that are not in the list of tested events + events.removeIf(event -> (Arrays.stream(getTestedEvents()).noneMatch(testedEvent -> (testedEvent.equals(event.getEventType().getName()))))); return events; } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java deleted file mode 100644 index 8667633738ea..000000000000 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/Stressor.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2022, 2022, Red Hat Inc. 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 com.oracle.svm.test.jfr; - -import java.util.ArrayList; -import java.util.List; - -/** - * Class to help run multiple threads executing some task. - */ -public class Stressor { - public static void execute(int numberOfThreads, Runnable task) throws Exception { - List threads = new ArrayList<>(); - for (int n = 0; n < numberOfThreads; ++n) { - Thread t = new Thread(task); - threads.add(t); - t.start(); - } - for (Thread t : threads) { - t.join(); - } - } -} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java index 40dd2fabfb27..04323f5cc878 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java @@ -28,9 +28,7 @@ import static org.junit.Assert.assertTrue; -import java.util.LinkedList; import java.util.List; -import java.util.Queue; import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedThread; @@ -46,14 +44,13 @@ public class TestJavaMonitorEnter extends JfrTest { static Thread firstThread; static Thread secondThread; static final Helper helper = new Helper(); - private static Queue orderedWaiterNames = new LinkedList<>(); @Override public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorEnter"}; } - public void validateEvents() throws Throwable{ + public void validateEvents() throws Throwable { List events; events = getEvents("jdk.JavaMonitorEnter"); int count = 0; @@ -61,19 +58,15 @@ public void validateEvents() throws Throwable{ for (RecordedEvent event : events) { RecordedObject struct = event; String eventThread = struct. getValue("eventThread").getJavaName(); - if (event.getEventType().getName().equals("jdk.JavaMonitorEnter") - && struct. getValue("monitorClass").getName().equals(Helper.class.getName()) - && event.getDuration().toMillis() >= MILLIS - && secondThread.getName().equals(eventThread)) { + if (struct. getValue("monitorClass").getName().equals(Helper.class.getName()) && event.getDuration().toMillis() >= MILLIS && secondThread.getName().equals(eventThread)) { // verify previous owner - assertTrue("Previous owner is wrong",struct. getValue("previousOwner").getJavaName().equals(firstThread.getName())); + assertTrue("Previous owner is wrong", struct. getValue("previousOwner").getJavaName().equals(firstThread.getName())); found = true; break; } } - assertTrue("Expected monitor blocked event not found" , found); - + assertTrue("Expected monitor blocked event not found", found); } @Test @@ -88,8 +81,8 @@ public void test() throws Exception { Runnable second = () -> { try { - //wait until lock is held - while(!inCritical) { + // wait until lock is held + while (!inCritical) { Thread.sleep(10); } helper.doWork(); @@ -116,7 +109,7 @@ private synchronized void doWork() throws InterruptedException { } // spin until second thread blocks - while(!secondThread.getState().equals(Thread.State.BLOCKED)) { + while (!secondThread.getState().equals(Thread.State.BLOCKED)) { Thread.sleep(10); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java index 0a5db97f8d0d..b3bacff5cf5c 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java @@ -30,8 +30,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.io.IOException; -import java.time.Duration; import java.util.List; import jdk.jfr.consumer.RecordedClass; @@ -53,7 +51,7 @@ public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; } - public void validateEvents() throws Throwable{ + public void validateEvents() throws Throwable { List events; events = getEvents("jdk.JavaMonitorWait"); @@ -65,7 +63,7 @@ public void validateEvents() throws Throwable{ String eventThread = struct. getValue("eventThread").getJavaName(); String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; assertTrue("No event thread", eventThread != null); - if ((!eventThread.equals(producerName) && !eventThread.equals(consumerName)) || !event.getEventType().getName().equals("jdk.JavaMonitorWait") || + if ((!eventThread.equals(producerName) && !eventThread.equals(consumerName)) || !struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { continue; } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java index ed53f5379fd9..d6b1ca908b5a 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java @@ -55,15 +55,12 @@ public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; } - public void validateEvents() throws Throwable{ + public void validateEvents() throws Throwable { List events; events = getEvents("TestJavaMonitorWaitInterrupt"); for (RecordedEvent event : events) { RecordedObject struct = event; - if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) { - continue; - } String eventThread = struct. getValue("eventThread").getJavaName(); String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; if (!eventThread.equals(interrupterThread.getName()) && @@ -90,7 +87,7 @@ public void validateEvents() throws Throwable{ simpleWaitFound && interruptedFound); } - private void testInterruption() throws Exception{ + private void testInterruption() throws Exception { Runnable interrupted = () -> { try { @@ -120,7 +117,7 @@ private void testInterruption() throws Exception{ interrupterThread.join(); } - private void testWaitNotify() throws Exception{ + private void testWaitNotify() throws Exception { Runnable simpleWaiter = () -> { helper.simpleWait(); }; @@ -131,7 +128,7 @@ private void testWaitNotify() throws Exception{ Thread.sleep(10); } helper.simpleNotify(); - }catch (Exception e) { + } catch (Exception e) { Assert.fail(e.getMessage()); } }; @@ -144,10 +141,10 @@ private void testWaitNotify() throws Exception{ simpleWaitThread.join(); simpleNotifyThread.join(); } + @Test public void test() throws Exception { testInterruption(); - System.out.println("*** testInterruption done"); testWaitNotify(); } @@ -162,7 +159,7 @@ public synchronized void interrupt() { try { Thread.sleep(MILLIS); interruptedThread.interrupt(); - }catch (Exception e) { + } catch (Exception e) { Assert.fail(e.getMessage()); } } @@ -176,8 +173,8 @@ public synchronized void simpleWait() { } public synchronized void simpleNotify() throws InterruptedException { - Thread.sleep(MILLIS); - notify(); + Thread.sleep(MILLIS); + notify(); } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java index 5c3777634f0c..44b19b2f07d2 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java @@ -29,8 +29,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.io.IOException; -import java.time.Duration; import java.util.List; import org.junit.Test; @@ -55,32 +53,28 @@ public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; } - public void validateEvents() throws Throwable{ + public void validateEvents() throws Throwable { List events = getEvents("TestJavaMonitorWaitNotifyAll"); - for (RecordedEvent event : events) { RecordedObject struct = event; - if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) { - continue; - } - String eventThread = struct.getValue("eventThread").getJavaName(); - String notifThread = struct.getValue("notifier") != null ? struct.getValue("notifier").getJavaName() : null; + String eventThread = struct. getValue("eventThread").getJavaName(); + String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; if (!eventThread.equals(producerThread1.getName()) && - !eventThread.equals(producerThread2.getName()) && - !eventThread.equals(consumerThread.getName())) { + !eventThread.equals(producerThread2.getName()) && + !eventThread.equals(consumerThread.getName())) { continue; } - if (!struct.getValue("monitorClass").getName().equals(Helper.class.getName())) { + if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { continue; } assertTrue("Event is wrong duration.", event.getDuration().toMillis() >= MILLIS); if (eventThread.equals(consumerThread.getName())) { - assertTrue("Should have timed out.", struct.getValue("timedOut").booleanValue()); + assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue()); notifierFound = true; } else { - assertFalse("Should not have timed out.", struct.getValue("timedOut").booleanValue()); + assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue()); assertTrue("Notifier thread name is incorrect", notifThread.equals(consumerThread.getName())); waitersFound++; } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java index 13e00b9d9dd3..2b443949e3b3 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java @@ -28,7 +28,6 @@ import static org.junit.Assert.assertTrue; - import java.util.List; import org.junit.Test; @@ -55,15 +54,12 @@ public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; } - public void validateEvents() throws Throwable{ + public void validateEvents() throws Throwable { List events; events = getEvents("TestJavaMonitorWaitTimeout"); for (RecordedEvent event : events) { RecordedObject struct = event; - if (!event.getEventType().getName().equals("jdk.JavaMonitorWait")) { - continue; - } String eventThread = struct. getValue("eventThread").getJavaName(); String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; if (!eventThread.equals(unheardNotifierThread.getName()) && @@ -75,7 +71,7 @@ public void validateEvents() throws Throwable{ if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { continue; } - assertTrue("Event is wrong duration:"+event.getDuration().toMillis(), event.getDuration().toMillis() >= MILLIS); + assertTrue("Event is wrong duration:" + event.getDuration().toMillis(), event.getDuration().toMillis() >= MILLIS); if (eventThread.equals(timeoutThread.getName())) { assertTrue("Notifier of timeout thread should be null", notifThread == null); assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue()); @@ -90,7 +86,6 @@ public void validateEvents() throws Throwable{ simpleWaitFound && timeoutFound); } - private void testTimeout() throws InterruptedException { Runnable unheardNotifier = () -> { try { @@ -114,15 +109,15 @@ private void testTimeout() throws InterruptedException { timeoutThread.start(); timeoutThread.join(); - //wait for timeout before trying to notify + // wait for timeout before trying to notify unheardNotifierThread.start(); unheardNotifierThread.join(); } - private void testWaitNotify() throws Exception{ + private void testWaitNotify() throws Exception { Runnable simpleWaiter = () -> { - helper.simpleWait(); + helper.simpleWait(); }; Runnable simpleNotifier = () -> { @@ -144,10 +139,10 @@ private void testWaitNotify() throws Exception{ simpleWaitThread.join(); simpleNotifyThread.join(); } + @Test public void test() throws Exception { testTimeout(); - System.out.println("timeout test done"); testWaitNotify(); } @@ -169,8 +164,8 @@ public synchronized void simpleWait() { } public synchronized void simpleNotify() throws InterruptedException { - Thread.sleep(MILLIS); - notify(); + Thread.sleep(MILLIS); + notify(); } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java index e0f5a2242405..4ccd02c4e01f 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java @@ -31,7 +31,6 @@ import jdk.jfr.consumer.RecordedThread; import java.io.IOException; -import java.time.Duration; import java.util.List; import static org.junit.Assert.assertTrue; @@ -55,9 +54,6 @@ public void validateEvents() { } boolean foundSleepEvent = false; for (RecordedEvent event : events) { - if (!event.getEventType().getName().equals("jdk.ThreadSleep")) { - continue; - } RecordedObject struct = event; String eventThread = struct. getValue("eventThread").getJavaName(); if (!eventThread.equals(sleepingThreadName)) { From 5b37c49ab731c1a2e2a77a9a563fbd84feb97928 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 7 Oct 2022 15:44:30 -0400 Subject: [PATCH 09/21] fixes for gate check --- .../oracle/svm/test/jfr/TestJavaMonitorEnter.java | 1 + .../oracle/svm/test/jfr/TestJavaMonitorWait.java | 1 + .../svm/test/jfr/TestJavaMonitorWaitInterrupt.java | 3 ++- .../svm/test/jfr/TestJavaMonitorWaitNotifyAll.java | 1 + .../svm/test/jfr/TestJavaMonitorWaitTimeout.java | 13 +++++-------- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java index 04323f5cc878..31b337c5cb8b 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java @@ -50,6 +50,7 @@ public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorEnter"}; } + @Override public void validateEvents() throws Throwable { List events; events = getEvents("jdk.JavaMonitorEnter"); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java index b3bacff5cf5c..64cbb358e9aa 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java @@ -51,6 +51,7 @@ public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; } + @Override public void validateEvents() throws Throwable { List events; events = getEvents("jdk.JavaMonitorWait"); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java index d6b1ca908b5a..f859748fcbf9 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java @@ -55,6 +55,7 @@ public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; } + @Override public void validateEvents() throws Throwable { List events; events = getEvents("TestJavaMonitorWaitInterrupt"); @@ -87,7 +88,7 @@ public void validateEvents() throws Throwable { simpleWaitFound && interruptedFound); } - private void testInterruption() throws Exception { + private static void testInterruption() throws Exception { Runnable interrupted = () -> { try { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java index 44b19b2f07d2..f8965e42d113 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java @@ -53,6 +53,7 @@ public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; } + @Override public void validateEvents() throws Throwable { List events = getEvents("TestJavaMonitorWaitNotifyAll"); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java index 2b443949e3b3..95c68b396873 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java @@ -54,6 +54,7 @@ public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; } + @Override public void validateEvents() throws Throwable { List events; events = getEvents("TestJavaMonitorWaitTimeout"); @@ -86,13 +87,9 @@ public void validateEvents() throws Throwable { simpleWaitFound && timeoutFound); } - private void testTimeout() throws InterruptedException { + private static void testTimeout() throws InterruptedException { Runnable unheardNotifier = () -> { - try { - helper.unheardNotify(); - } catch (InterruptedException e) { - Assert.fail(e.getMessage()); - } + helper.unheardNotify(); }; Runnable timouter = () -> { @@ -115,7 +112,7 @@ private void testTimeout() throws InterruptedException { } - private void testWaitNotify() throws Exception { + private static void testWaitNotify() throws Exception { Runnable simpleWaiter = () -> { helper.simpleWait(); }; @@ -151,7 +148,7 @@ public synchronized void timeout() throws InterruptedException { wait(MILLIS); } - public synchronized void unheardNotify() throws InterruptedException { + public synchronized void unheardNotify() { notify(); } From 4e26325fd18a6066ab6663de85c81b02083df41a Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 7 Oct 2022 16:03:18 -0400 Subject: [PATCH 10/21] more fixes for gate --- .../src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java | 1 - .../com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java | 2 +- .../src/com/oracle/svm/test/jfr/TestThreadSleep.java | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java index 31b337c5cb8b..d8e9284bb15a 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java @@ -54,7 +54,6 @@ public String[] getTestedEvents() { public void validateEvents() throws Throwable { List events; events = getEvents("jdk.JavaMonitorEnter"); - int count = 0; boolean found = false; for (RecordedEvent event : events) { RecordedObject struct = event; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java index f859748fcbf9..bcf2b32e6478 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java @@ -118,7 +118,7 @@ private static void testInterruption() throws Exception { interrupterThread.join(); } - private void testWaitNotify() throws Exception { + private static void testWaitNotify() throws Exception { Runnable simpleWaiter = () -> { helper.simpleWait(); }; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java index 4ccd02c4e01f..d915b6ee9406 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java @@ -45,6 +45,7 @@ public String[] getTestedEvents() { return new String[]{"jdk.ThreadSleep"}; } + @Override public void validateEvents() { List events; try { From 417d9f714d55c5f03bc5da4d5dd0cd0ec7e9bd72 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 11 Oct 2022 10:43:15 -0400 Subject: [PATCH 11/21] set flag before blocking/waiting --- .../src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java | 8 +++++--- .../oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java | 8 ++++++-- .../oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java | 4 +++- .../oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java | 4 +++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java index d8e9284bb15a..1aac13f6f09f 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java @@ -29,6 +29,7 @@ import static org.junit.Assert.assertTrue; import java.util.List; +import java.util.concurrent.locks.ReentrantLock; import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedThread; @@ -40,7 +41,8 @@ public class TestJavaMonitorEnter extends JfrTest { private static final int MILLIS = 60; - static boolean inCritical = false; + static volatile boolean inCritical = false; + static volatile boolean blockedAtCritical = false; static Thread firstThread; static Thread secondThread; static final Helper helper = new Helper(); @@ -85,12 +87,12 @@ public void test() throws Exception { while (!inCritical) { Thread.sleep(10); } + blockedAtCritical = true; helper.doWork(); } catch (InterruptedException e) { throw new RuntimeException(e); } }; - firstThread = new Thread(first); secondThread = new Thread(second); firstThread.start(); @@ -109,7 +111,7 @@ private synchronized void doWork() throws InterruptedException { } // spin until second thread blocks - while (!secondThread.getState().equals(Thread.State.BLOCKED)) { + while (!secondThread.getState().equals(Thread.State.BLOCKED) || !blockedAtCritical) { Thread.sleep(10); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java index bcf2b32e6478..8f108a86c230 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java @@ -49,6 +49,7 @@ public class TestJavaMonitorWaitInterrupt extends JfrTest { private boolean interruptedFound = false; private boolean simpleWaitFound = false; + static volatile boolean waiting = false; @Override public String[] getTestedEvents() { @@ -102,7 +103,7 @@ private static void testInterruption() throws Exception { Runnable interrupter = () -> { try { - while (!interruptedThread.getState().equals(Thread.State.WAITING)) { + while (!interruptedThread.getState().equals(Thread.State.WAITING) || !waiting) { Thread.sleep(10); } } catch (Exception e) { @@ -116,6 +117,7 @@ private static void testInterruption() throws Exception { interrupterThread.start(); interruptedThread.join(); interrupterThread.join(); + waiting = false; } private static void testWaitNotify() throws Exception { @@ -125,7 +127,7 @@ private static void testWaitNotify() throws Exception { Runnable simpleNotifier = () -> { try { - while (!simpleWaitThread.getState().equals(Thread.State.WAITING)) { + while (!simpleWaitThread.getState().equals(Thread.State.WAITING) || !waiting) { Thread.sleep(10); } helper.simpleNotify(); @@ -153,6 +155,7 @@ static class Helper { public Thread interrupted; public synchronized void interrupted() throws InterruptedException { + waiting = true; wait(); } @@ -167,6 +170,7 @@ public synchronized void interrupt() { public synchronized void simpleWait() { try { + waiting = true; wait(); } catch (Exception e) { Assert.fail(e.getMessage()); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java index f8965e42d113..326647b97b6f 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java @@ -47,6 +47,7 @@ public class TestJavaMonitorWaitNotifyAll extends JfrTest { private boolean notifierFound = false; private int waitersFound = 0; + static volatile int waiting = 0; @Override public String[] getTestedEvents() { @@ -99,7 +100,7 @@ public void test() throws Exception { producerThread2 = new Thread(producer); Runnable consumer = () -> { try { - while (!producerThread1.getState().equals(Thread.State.WAITING) || !producerThread2.getState().equals(Thread.State.WAITING)) { + while (!producerThread1.getState().equals(Thread.State.WAITING) || !producerThread2.getState().equals(Thread.State.WAITING) || waiting < 2) { Thread.sleep(10); } helper.consume(); @@ -120,6 +121,7 @@ public void test() throws Exception { static class Helper { public synchronized void produce() throws InterruptedException { + waiting++; wait(); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java index 95c68b396873..7c570994a82a 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java @@ -48,6 +48,7 @@ public class TestJavaMonitorWaitTimeout extends JfrTest { static Thread simpleNotifyThread; private boolean timeoutFound = false; private boolean simpleWaitFound = false; + static volatile boolean waiting = false; @Override public String[] getTestedEvents() { @@ -119,7 +120,7 @@ private static void testWaitNotify() throws Exception { Runnable simpleNotifier = () -> { try { - while (!simpleWaitThread.getState().equals(Thread.State.WAITING)) { + while (!simpleWaitThread.getState().equals(Thread.State.WAITING) || !waiting) { Thread.sleep(10); } helper.simpleNotify(); @@ -154,6 +155,7 @@ public synchronized void unheardNotify() { public synchronized void simpleWait() { try { + waiting = true; wait(); } catch (Exception e) { Assert.fail(e.getMessage()); From 2902c0bca1e8bf1ee4462ca47e14f7ae3843e4bd Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 11 Oct 2022 13:50:45 -0400 Subject: [PATCH 12/21] update wait tests --- .../jfr/TestJavaMonitorWaitInterrupt.java | 53 +++++++++---------- .../jfr/TestJavaMonitorWaitNotifyAll.java | 22 ++++---- .../test/jfr/TestJavaMonitorWaitTimeout.java | 28 +++++----- 3 files changed, 50 insertions(+), 53 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java index 8f108a86c230..2610e97fb608 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java @@ -49,7 +49,7 @@ public class TestJavaMonitorWaitInterrupt extends JfrTest { private boolean interruptedFound = false; private boolean simpleWaitFound = false; - static volatile boolean waiting = false; + static volatile boolean inCritical = false; @Override public String[] getTestedEvents() { @@ -74,7 +74,7 @@ public void validateEvents() throws Throwable { if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { continue; } - assertTrue("Event is wrong duration.", event.getDuration().toMillis() >= MILLIS); + assertTrue("Event is wrong duration." + event.getDuration().toMillis(), event.getDuration().toMillis() >= MILLIS); assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue()); if (eventThread.equals(interruptedThread.getName())) { @@ -93,7 +93,7 @@ private static void testInterruption() throws Exception { Runnable interrupted = () -> { try { - helper.interrupted(); + helper.interrupt();// must enter first throw new RuntimeException("Was not interrupted!!"); } catch (InterruptedException e) { // should get interrupted @@ -103,13 +103,13 @@ private static void testInterruption() throws Exception { Runnable interrupter = () -> { try { - while (!interruptedThread.getState().equals(Thread.State.WAITING) || !waiting) { + while (!inCritical) { Thread.sleep(10); } - } catch (Exception e) { + helper.interrupt(); + } catch (InterruptedException e) { Assert.fail(e.getMessage()); } - helper.interrupt(); }; interrupterThread = new Thread(interrupter); @@ -117,17 +117,20 @@ private static void testInterruption() throws Exception { interrupterThread.start(); interruptedThread.join(); interrupterThread.join(); - waiting = false; } private static void testWaitNotify() throws Exception { Runnable simpleWaiter = () -> { - helper.simpleWait(); + try { + helper.simpleNotify(); + } catch (InterruptedException e) { + Assert.fail(e.getMessage()); + } }; Runnable simpleNotifier = () -> { try { - while (!simpleWaitThread.getState().equals(Thread.State.WAITING) || !waiting) { + while (!inCritical) { Thread.sleep(10); } helper.simpleNotify(); @@ -148,38 +151,32 @@ private static void testWaitNotify() throws Exception { @Test public void test() throws Exception { testInterruption(); + inCritical = false; // reset testWaitNotify(); } static class Helper { public Thread interrupted; - public synchronized void interrupted() throws InterruptedException { - waiting = true; - wait(); - } - - public synchronized void interrupt() { - try { + public synchronized void interrupt() throws InterruptedException { + if (Thread.currentThread().equals(interruptedThread)) { + inCritical = true; // Ensure T1 enters critical section first + wait(); // allow T2 to enter section + } else if (Thread.currentThread().equals(interrupterThread)) { + // If T2 is in the critical section T1 is already waiting. Thread.sleep(MILLIS); interruptedThread.interrupt(); - } catch (Exception e) { - Assert.fail(e.getMessage()); } } - public synchronized void simpleWait() { - try { - waiting = true; + public synchronized void simpleNotify() throws InterruptedException { + if (Thread.currentThread().equals(simpleWaitThread)) { + inCritical = true; wait(); - } catch (Exception e) { - Assert.fail(e.getMessage()); + } else if (Thread.currentThread().equals(simpleNotifyThread)) { + Thread.sleep(MILLIS); + notify(); } } - - public synchronized void simpleNotify() throws InterruptedException { - Thread.sleep(MILLIS); - notify(); - } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java index 326647b97b6f..d10bc2934a12 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java @@ -90,7 +90,7 @@ public void validateEvents() throws Throwable { public void test() throws Exception { Runnable producer = () -> { try { - helper.produce(); + helper.doWork(); } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -100,10 +100,10 @@ public void test() throws Exception { producerThread2 = new Thread(producer); Runnable consumer = () -> { try { - while (!producerThread1.getState().equals(Thread.State.WAITING) || !producerThread2.getState().equals(Thread.State.WAITING) || waiting < 2) { + while (waiting < 2) { Thread.sleep(10); } - helper.consume(); + helper.doWork(); } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -120,14 +120,14 @@ public void test() throws Exception { } static class Helper { - public synchronized void produce() throws InterruptedException { - waiting++; - wait(); - } - - public synchronized void consume() throws InterruptedException { - wait(MILLIS); - notifyAll(); // should wake up both producers + public synchronized void doWork() throws InterruptedException { + if (Thread.currentThread().equals(consumerThread)) { + wait(MILLIS); + notifyAll(); // should wake up both producers + } else { + waiting++; + wait(); + } } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java index 7c570994a82a..dbfef958f76b 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java @@ -48,7 +48,7 @@ public class TestJavaMonitorWaitTimeout extends JfrTest { static Thread simpleNotifyThread; private boolean timeoutFound = false; private boolean simpleWaitFound = false; - static volatile boolean waiting = false; + static volatile boolean inCritical = false; @Override public String[] getTestedEvents() { @@ -115,16 +115,20 @@ private static void testTimeout() throws InterruptedException { private static void testWaitNotify() throws Exception { Runnable simpleWaiter = () -> { - helper.simpleWait(); + try { + helper.simpleNotify(); + } catch (InterruptedException e) { + Assert.fail(e.getMessage()); + } }; Runnable simpleNotifier = () -> { try { - while (!simpleWaitThread.getState().equals(Thread.State.WAITING) || !waiting) { + while (!inCritical) { Thread.sleep(10); } helper.simpleNotify(); - } catch (InterruptedException e) { + } catch (Exception e) { Assert.fail(e.getMessage()); } }; @@ -153,18 +157,14 @@ public synchronized void unheardNotify() { notify(); } - public synchronized void simpleWait() { - try { - waiting = true; + public synchronized void simpleNotify() throws InterruptedException { + if (Thread.currentThread().equals(simpleWaitThread)) { + inCritical = true; wait(); - } catch (Exception e) { - Assert.fail(e.getMessage()); + } else if (Thread.currentThread().equals(simpleNotifyThread)) { + Thread.sleep(MILLIS); + notify(); } } - - public synchronized void simpleNotify() throws InterruptedException { - Thread.sleep(MILLIS); - notify(); - } } } From 4955e216db10acce661fe37c9d145e075f1a9da8 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 11 Oct 2022 14:22:12 -0400 Subject: [PATCH 13/21] fix error and checkstyle --- .../src/com/oracle/svm/test/jfr/JfrTest.java | 3 --- .../src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java | 1 - .../oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java | 2 +- .../src/com/oracle/svm/test/jfr/TestThreadSleep.java | 6 +++--- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java index e4c2ae660734..119f4922a664 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java @@ -85,7 +85,6 @@ public void endRecording() { } catch (Exception e) { Assert.fail("Fail to stop recording! Cause: " + e.getMessage()); } - checkEvents(); try { validateEvents(); } catch (Throwable throwable) { @@ -110,8 +109,6 @@ private void enableEvents() { } } - public abstract String[] getTestedEvents(); - public void validateEvents() throws Throwable { } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java index 1aac13f6f09f..42d2ef5d1c32 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java @@ -29,7 +29,6 @@ import static org.junit.Assert.assertTrue; import java.util.List; -import java.util.concurrent.locks.ReentrantLock; import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedThread; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java index 2610e97fb608..da89bb44cbd0 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java @@ -93,7 +93,7 @@ private static void testInterruption() throws Exception { Runnable interrupted = () -> { try { - helper.interrupt();// must enter first + helper.interrupt(); // must enter first throw new RuntimeException("Was not interrupted!!"); } catch (InterruptedException e) { // should get interrupted diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java index d915b6ee9406..e474bc1e6aeb 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java @@ -37,7 +37,7 @@ import org.junit.Test; public class TestThreadSleep extends JfrTest { - private String sleepingThreadName; + private Thread sleepingThread; private static final int MILLIS = 50; @Override @@ -57,7 +57,7 @@ public void validateEvents() { for (RecordedEvent event : events) { RecordedObject struct = event; String eventThread = struct. getValue("eventThread").getJavaName(); - if (!eventThread.equals(sleepingThreadName)) { + if (!eventThread.equals(sleepingThread.getName())) { continue; } if (event.getDuration().toMillis() < MILLIS) { @@ -71,7 +71,7 @@ public void validateEvents() { @Test public void test() throws Exception { - sleepingThreadName = Thread.currentThread().getName(); + sleepingThread = Thread.currentThread(); Thread.sleep(MILLIS); } } From cd40dc267ca88fe4ee954b984bdc9595122c8f68 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Tue, 11 Oct 2022 15:02:37 -0400 Subject: [PATCH 14/21] update visibility --- .../src/com/oracle/svm/test/jfr/JfrTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java index 119f4922a664..81efa0e0c80d 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java @@ -56,8 +56,8 @@ /** Base class for JFR unit tests. */ public abstract class JfrTest { - protected Jfr jfr; - protected Recording recording; + private Jfr jfr; + private Recording recording; private final ChronologicalComparator chronologicalComparator = new ChronologicalComparator(); @BeforeClass @@ -112,7 +112,7 @@ private void enableEvents() { public void validateEvents() throws Throwable { } - protected void checkEvents() { + private void checkEvents() { HashSet seenEvents = new HashSet<>(); try (RecordingFile recordingFile = new RecordingFile(recording.getDestination())) { while (recordingFile.hasMoreEvents()) { From 656ddfd9ee352bd999a287cceaef5418fa66ea0f Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 12 Oct 2022 15:55:22 -0400 Subject: [PATCH 15/21] rename variable. Have child threads start eachother --- .../svm/test/jfr/TestJavaMonitorEnter.java | 19 +++++-------------- .../jfr/TestJavaMonitorWaitInterrupt.java | 16 +++------------- .../jfr/TestJavaMonitorWaitNotifyAll.java | 14 +++++--------- .../test/jfr/TestJavaMonitorWaitTimeout.java | 9 +-------- 4 files changed, 14 insertions(+), 44 deletions(-) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java index 42d2ef5d1c32..66df0367e616 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java @@ -39,9 +39,7 @@ public class TestJavaMonitorEnter extends JfrTest { private static final int MILLIS = 60; - - static volatile boolean inCritical = false; - static volatile boolean blockedAtCritical = false; + static volatile boolean passedCheckpoint = false; static Thread firstThread; static Thread secondThread; static final Helper helper = new Helper(); @@ -82,11 +80,7 @@ public void test() throws Exception { Runnable second = () -> { try { - // wait until lock is held - while (!inCritical) { - Thread.sleep(10); - } - blockedAtCritical = true; + passedCheckpoint = true; helper.doWork(); } catch (InterruptedException e) { throw new RuntimeException(e); @@ -95,7 +89,6 @@ public void test() throws Exception { firstThread = new Thread(first); secondThread = new Thread(second); firstThread.start(); - secondThread.start(); firstThread.join(); secondThread.join(); @@ -103,19 +96,17 @@ public void test() throws Exception { static class Helper { private synchronized void doWork() throws InterruptedException { - inCritical = true; if (Thread.currentThread().equals(secondThread)) { - inCritical = false; return; // second thread doesn't need to do work. } + // ensure ordering of critical section entry + secondThread.start(); // spin until second thread blocks - while (!secondThread.getState().equals(Thread.State.BLOCKED) || !blockedAtCritical) { + while (!secondThread.getState().equals(Thread.State.BLOCKED) || !passedCheckpoint) { Thread.sleep(10); } - Thread.sleep(MILLIS); - inCritical = false; } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java index da89bb44cbd0..959665391729 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java @@ -46,10 +46,8 @@ public class TestJavaMonitorWaitInterrupt extends JfrTest { static Thread interrupterThread; static Thread simpleWaitThread; static Thread simpleNotifyThread; - private boolean interruptedFound = false; private boolean simpleWaitFound = false; - static volatile boolean inCritical = false; @Override public String[] getTestedEvents() { @@ -103,9 +101,6 @@ private static void testInterruption() throws Exception { Runnable interrupter = () -> { try { - while (!inCritical) { - Thread.sleep(10); - } helper.interrupt(); } catch (InterruptedException e) { Assert.fail(e.getMessage()); @@ -114,7 +109,6 @@ private static void testInterruption() throws Exception { interrupterThread = new Thread(interrupter); interruptedThread.start(); - interrupterThread.start(); interruptedThread.join(); interrupterThread.join(); } @@ -130,9 +124,6 @@ private static void testWaitNotify() throws Exception { Runnable simpleNotifier = () -> { try { - while (!inCritical) { - Thread.sleep(10); - } helper.simpleNotify(); } catch (Exception e) { Assert.fail(e.getMessage()); @@ -143,7 +134,6 @@ private static void testWaitNotify() throws Exception { simpleNotifyThread = new Thread(simpleNotifier); simpleWaitThread.start(); - simpleNotifyThread.start(); simpleWaitThread.join(); simpleNotifyThread.join(); } @@ -151,7 +141,6 @@ private static void testWaitNotify() throws Exception { @Test public void test() throws Exception { testInterruption(); - inCritical = false; // reset testWaitNotify(); } @@ -160,7 +149,8 @@ static class Helper { public synchronized void interrupt() throws InterruptedException { if (Thread.currentThread().equals(interruptedThread)) { - inCritical = true; // Ensure T1 enters critical section first + // Ensure T1 enters critical section first + interrupterThread.start(); wait(); // allow T2 to enter section } else if (Thread.currentThread().equals(interrupterThread)) { // If T2 is in the critical section T1 is already waiting. @@ -171,7 +161,7 @@ public synchronized void interrupt() throws InterruptedException { public synchronized void simpleNotify() throws InterruptedException { if (Thread.currentThread().equals(simpleWaitThread)) { - inCritical = true; + simpleNotifyThread.start(); wait(); } else if (Thread.currentThread().equals(simpleNotifyThread)) { Thread.sleep(MILLIS); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java index d10bc2934a12..ca225eb1ccd1 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java @@ -47,7 +47,6 @@ public class TestJavaMonitorWaitNotifyAll extends JfrTest { private boolean notifierFound = false; private int waitersFound = 0; - static volatile int waiting = 0; @Override public String[] getTestedEvents() { @@ -100,9 +99,6 @@ public void test() throws Exception { producerThread2 = new Thread(producer); Runnable consumer = () -> { try { - while (waiting < 2) { - Thread.sleep(10); - } helper.doWork(); } catch (InterruptedException e) { throw new RuntimeException(e); @@ -110,10 +106,7 @@ public void test() throws Exception { }; consumerThread = new Thread(consumer); - consumerThread.start(); producerThread1.start(); - producerThread2.start(); - consumerThread.join(); producerThread1.join(); producerThread2.join(); @@ -124,8 +117,11 @@ public synchronized void doWork() throws InterruptedException { if (Thread.currentThread().equals(consumerThread)) { wait(MILLIS); notifyAll(); // should wake up both producers - } else { - waiting++; + } else if (Thread.currentThread().equals(producerThread1)){ + producerThread2.start(); + wait(); + } else if (Thread.currentThread().equals(producerThread2)){ + consumerThread.start(); wait(); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java index dbfef958f76b..ff367fd236fb 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java @@ -43,13 +43,10 @@ public class TestJavaMonitorWaitTimeout extends JfrTest { static final Helper helper = new Helper(); static Thread unheardNotifierThread; static Thread timeoutThread; - static Thread simpleWaitThread; static Thread simpleNotifyThread; private boolean timeoutFound = false; private boolean simpleWaitFound = false; - static volatile boolean inCritical = false; - @Override public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; @@ -124,9 +121,6 @@ private static void testWaitNotify() throws Exception { Runnable simpleNotifier = () -> { try { - while (!inCritical) { - Thread.sleep(10); - } helper.simpleNotify(); } catch (Exception e) { Assert.fail(e.getMessage()); @@ -137,7 +131,6 @@ private static void testWaitNotify() throws Exception { simpleNotifyThread = new Thread(simpleNotifier); simpleWaitThread.start(); - simpleNotifyThread.start(); simpleWaitThread.join(); simpleNotifyThread.join(); } @@ -159,7 +152,7 @@ public synchronized void unheardNotify() { public synchronized void simpleNotify() throws InterruptedException { if (Thread.currentThread().equals(simpleWaitThread)) { - inCritical = true; + simpleNotifyThread.start(); wait(); } else if (Thread.currentThread().equals(simpleNotifyThread)) { Thread.sleep(MILLIS); From a5995e78d294283033ca4004fc3dfc1e646c22ee Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Fri, 14 Oct 2022 13:25:58 +0200 Subject: [PATCH 16/21] Cleanups. --- .../src/com/oracle/svm/test/jfr/JfrTest.java | 11 ++--- .../oracle/svm/test/jfr/TestClassEvent.java | 3 +- ...er.java => TestJavaMonitorEnterEvent.java} | 33 ++++++------- ...ait.java => TestJavaMonitorWaitEvent.java} | 28 +++++------ ...=> TestJavaMonitorWaitInterruptEvent.java} | 46 ++++++++----------- ...=> TestJavaMonitorWaitNotifyAllEvent.java} | 44 ++++++++---------- ...a => TestJavaMonitorWaitTimeoutEvent.java} | 44 ++++++++---------- .../svm/test/jfr/TestStackTraceEvent.java | 17 ------- .../oracle/svm/test/jfr/TestStringEvent.java | 3 +- ...adSleep.java => TestThreadSleepEvent.java} | 27 ++++------- 10 files changed, 104 insertions(+), 152 deletions(-) rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestJavaMonitorEnter.java => TestJavaMonitorEnterEvent.java} (77%) rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestJavaMonitorWait.java => TestJavaMonitorWaitEvent.java} (84%) rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestJavaMonitorWaitInterrupt.java => TestJavaMonitorWaitInterruptEvent.java} (81%) rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestJavaMonitorWaitNotifyAll.java => TestJavaMonitorWaitNotifyAllEvent.java} (77%) rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestJavaMonitorWaitTimeout.java => TestJavaMonitorWaitTimeoutEvent.java} (80%) rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/{TestThreadSleep.java => TestThreadSleepEvent.java} (81%) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java index 81efa0e0c80d..0e4aff2e6002 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java @@ -33,10 +33,11 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; -import java.util.Comparator; +import com.oracle.svm.util.ClassUtil; import org.graalvm.nativeimage.ImageInfo; import org.graalvm.nativeimage.hosted.Feature; import org.junit.After; @@ -58,7 +59,6 @@ public abstract class JfrTest { private Jfr jfr; private Recording recording; - private final ChronologicalComparator chronologicalComparator = new ChronologicalComparator(); @BeforeClass public static void checkForJFR() { @@ -159,15 +159,14 @@ private Path makeCopy(String testName) throws IOException { // from jdk 19 return p; } - protected List getEvents(String testName) throws IOException { - Path p = makeCopy(testName); + protected List getEvents() throws IOException { + Path p = makeCopy(ClassUtil.getUnqualifiedName(getClass())); List events = RecordingFile.readAllEvents(p); - Collections.sort(events, chronologicalComparator); + Collections.sort(events, new ChronologicalComparator()); // remove events that are not in the list of tested events events.removeIf(event -> (Arrays.stream(getTestedEvents()).noneMatch(testedEvent -> (testedEvent.equals(event.getEventType().getName()))))); return events; } - } class JfrTestFeature implements Feature { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java index 04f5f9234afc..88f0f1e30aa3 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestClassEvent.java @@ -26,9 +26,10 @@ package com.oracle.svm.test.jfr; -import com.oracle.svm.test.jfr.events.ClassEvent; import org.junit.Test; +import com.oracle.svm.test.jfr.events.ClassEvent; + public class TestClassEvent extends JfrTest { @Override diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java similarity index 77% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java index 66df0367e616..fa14c37c90f9 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnter.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorEnterEvent.java @@ -28,21 +28,19 @@ import static org.junit.Assert.assertTrue; -import java.util.List; - -import jdk.jfr.consumer.RecordedClass; -import jdk.jfr.consumer.RecordedThread; import org.junit.Test; +import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedObject; +import jdk.jfr.consumer.RecordedThread; -public class TestJavaMonitorEnter extends JfrTest { +public class TestJavaMonitorEnterEvent extends JfrTest { private static final int MILLIS = 60; - static volatile boolean passedCheckpoint = false; - static Thread firstThread; - static Thread secondThread; - static final Helper helper = new Helper(); + + private final Helper helper = new Helper(); + private Thread firstThread; + private Thread secondThread; + private volatile boolean passedCheckpoint; @Override public String[] getTestedEvents() { @@ -51,16 +49,12 @@ public String[] getTestedEvents() { @Override public void validateEvents() throws Throwable { - List events; - events = getEvents("jdk.JavaMonitorEnter"); boolean found = false; - for (RecordedEvent event : events) { - RecordedObject struct = event; - String eventThread = struct. getValue("eventThread").getJavaName(); - if (struct. getValue("monitorClass").getName().equals(Helper.class.getName()) && event.getDuration().toMillis() >= MILLIS && secondThread.getName().equals(eventThread)) { - + for (RecordedEvent event : getEvents()) { + String eventThread = event. getValue("eventThread").getJavaName(); + if (event. getValue("monitorClass").getName().equals(Helper.class.getName()) && event.getDuration().toMillis() >= MILLIS && secondThread.getName().equals(eventThread)) { // verify previous owner - assertTrue("Previous owner is wrong", struct. getValue("previousOwner").getJavaName().equals(firstThread.getName())); + assertTrue("Previous owner is wrong", event. getValue("previousOwner").getJavaName().equals(firstThread.getName())); found = true; break; } @@ -88,13 +82,14 @@ public void test() throws Exception { }; firstThread = new Thread(first); secondThread = new Thread(second); + firstThread.start(); firstThread.join(); secondThread.join(); } - static class Helper { + private class Helper { private synchronized void doWork() throws InterruptedException { if (Thread.currentThread().equals(secondThread)) { return; // second thread doesn't need to do work. diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitEvent.java similarity index 84% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitEvent.java index 64cbb358e9aa..e9521c824c7d 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWait.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitEvent.java @@ -30,21 +30,19 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.util.List; - -import jdk.jfr.consumer.RecordedClass; import org.junit.Test; +import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedObject; import jdk.jfr.consumer.RecordedThread; -public class TestJavaMonitorWait extends JfrTest { +public class TestJavaMonitorWaitEvent extends JfrTest { private static final int MILLIS = 50; private static final int COUNT = 10; + + private final Helper helper = new Helper(); private String producerName; private String consumerName; - static final Helper helper = new Helper(); @Override public String[] getTestedEvents() { @@ -53,24 +51,20 @@ public String[] getTestedEvents() { @Override public void validateEvents() throws Throwable { - List events; - events = getEvents("jdk.JavaMonitorWait"); - int prodCount = 0; int consCount = 0; String lastEventThreadName = null; // should alternate if buffer is 1 - for (RecordedEvent event : events) { - RecordedObject struct = event; - String eventThread = struct. getValue("eventThread").getJavaName(); - String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; + for (RecordedEvent event : getEvents()) { + String eventThread = event. getValue("eventThread").getJavaName(); + String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null; assertTrue("No event thread", eventThread != null); if ((!eventThread.equals(producerName) && !eventThread.equals(consumerName)) || - !struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { + !event. getValue("monitorClass").getName().equals(Helper.class.getName())) { continue; } assertTrue("Wrong event duration", event.getDuration().toMillis() >= MILLIS); - assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue()); + assertFalse("Should not have timed out.", event. getValue("timedOut").booleanValue()); if (lastEventThreadName == null) { lastEventThreadName = notifThread; @@ -110,13 +104,15 @@ public void test() throws Exception { Thread tp = new Thread(producer); producerName = tp.getName(); consumerName = tc.getName(); + tp.start(); tc.start(); + tp.join(); tc.join(); } - static class Helper { + private class Helper { private int count = 0; private final int bufferSize = 1; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterruptEvent.java similarity index 81% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterruptEvent.java index 959665391729..99c82f4dd0d4 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterrupt.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitInterruptEvent.java @@ -29,25 +29,23 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.util.List; - -import org.junit.Test; import org.junit.Assert; +import org.junit.Test; import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedObject; import jdk.jfr.consumer.RecordedThread; -public class TestJavaMonitorWaitInterrupt extends JfrTest { +public class TestJavaMonitorWaitInterruptEvent extends JfrTest { private static final int MILLIS = 50; - static final Helper helper = new Helper(); - static Thread interruptedThread; - static Thread interrupterThread; - static Thread simpleWaitThread; - static Thread simpleNotifyThread; - private boolean interruptedFound = false; - private boolean simpleWaitFound = false; + + private Helper helper = new Helper(); + private Thread interruptedThread; + private Thread interrupterThread; + private Thread simpleWaitThread; + private Thread simpleNotifyThread; + private boolean interruptedFound; + private boolean simpleWaitFound; @Override public String[] getTestedEvents() { @@ -56,24 +54,20 @@ public String[] getTestedEvents() { @Override public void validateEvents() throws Throwable { - List events; - events = getEvents("TestJavaMonitorWaitInterrupt"); - - for (RecordedEvent event : events) { - RecordedObject struct = event; - String eventThread = struct. getValue("eventThread").getJavaName(); - String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; + for (RecordedEvent event : getEvents()) { + String eventThread = event. getValue("eventThread").getJavaName(); + String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null; if (!eventThread.equals(interrupterThread.getName()) && !eventThread.equals(interruptedThread.getName()) && !eventThread.equals(simpleNotifyThread.getName()) && !eventThread.equals(simpleWaitThread.getName())) { continue; } - if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { + if (!event. getValue("monitorClass").getName().equals(Helper.class.getName())) { continue; } assertTrue("Event is wrong duration." + event.getDuration().toMillis(), event.getDuration().toMillis() >= MILLIS); - assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue()); + assertFalse("Should not have timed out.", event. getValue("timedOut").booleanValue()); if (eventThread.equals(interruptedThread.getName())) { assertTrue("Notifier of interrupted thread should be null", notifThread == null); @@ -87,8 +81,7 @@ public void validateEvents() throws Throwable { simpleWaitFound && interruptedFound); } - private static void testInterruption() throws Exception { - + private void testInterruption() throws Exception { Runnable interrupted = () -> { try { helper.interrupt(); // must enter first @@ -113,7 +106,7 @@ private static void testInterruption() throws Exception { interrupterThread.join(); } - private static void testWaitNotify() throws Exception { + private void testWaitNotify() throws Exception { Runnable simpleWaiter = () -> { try { helper.simpleNotify(); @@ -134,6 +127,7 @@ private static void testWaitNotify() throws Exception { simpleNotifyThread = new Thread(simpleNotifier); simpleWaitThread.start(); + simpleWaitThread.join(); simpleNotifyThread.join(); } @@ -144,9 +138,7 @@ public void test() throws Exception { testWaitNotify(); } - static class Helper { - public Thread interrupted; - + private class Helper { public synchronized void interrupt() throws InterruptedException { if (Thread.currentThread().equals(interruptedThread)) { // Ensure T1 enters critical section first diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAllEvent.java similarity index 77% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAllEvent.java index ca225eb1ccd1..5a70dbd44b2b 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAll.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitNotifyAllEvent.java @@ -29,24 +29,21 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.util.List; - import org.junit.Test; import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedObject; import jdk.jfr.consumer.RecordedThread; -public class TestJavaMonitorWaitNotifyAll extends JfrTest { +public class TestJavaMonitorWaitNotifyAllEvent extends JfrTest { private static final int MILLIS = 50; - static final Helper helper = new Helper(); - static Thread producerThread1; - static Thread producerThread2; - static Thread consumerThread; - private boolean notifierFound = false; - private int waitersFound = 0; + private Helper helper = new Helper(); + private Thread producerThread1; + private Thread producerThread2; + private Thread consumerThread; + private boolean notifierFound; + private int waitersFound; @Override public String[] getTestedEvents() { @@ -55,27 +52,24 @@ public String[] getTestedEvents() { @Override public void validateEvents() throws Throwable { - List events = getEvents("TestJavaMonitorWaitNotifyAll"); - - for (RecordedEvent event : events) { - RecordedObject struct = event; - String eventThread = struct. getValue("eventThread").getJavaName(); - String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; + for (RecordedEvent event : getEvents()) { + String eventThread = event. getValue("eventThread").getJavaName(); + String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null; if (!eventThread.equals(producerThread1.getName()) && !eventThread.equals(producerThread2.getName()) && !eventThread.equals(consumerThread.getName())) { continue; } - if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { + if (!event. getValue("monitorClass").getName().equals(Helper.class.getName())) { continue; } assertTrue("Event is wrong duration.", event.getDuration().toMillis() >= MILLIS); if (eventThread.equals(consumerThread.getName())) { - assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue()); + assertTrue("Should have timed out.", event. getValue("timedOut").booleanValue()); notifierFound = true; } else { - assertFalse("Should not have timed out.", struct. getValue("timedOut").booleanValue()); + assertFalse("Should not have timed out.", event. getValue("timedOut").booleanValue()); assertTrue("Notifier thread name is incorrect", notifThread.equals(consumerThread.getName())); waitersFound++; } @@ -95,8 +89,6 @@ public void test() throws Exception { } }; - producerThread1 = new Thread(producer); - producerThread2 = new Thread(producer); Runnable consumer = () -> { try { helper.doWork(); @@ -105,22 +97,26 @@ public void test() throws Exception { } }; + producerThread1 = new Thread(producer); + producerThread2 = new Thread(producer); consumerThread = new Thread(consumer); + producerThread1.start(); + consumerThread.join(); producerThread1.join(); producerThread2.join(); } - static class Helper { + private class Helper { public synchronized void doWork() throws InterruptedException { if (Thread.currentThread().equals(consumerThread)) { wait(MILLIS); notifyAll(); // should wake up both producers - } else if (Thread.currentThread().equals(producerThread1)){ + } else if (Thread.currentThread().equals(producerThread1)) { producerThread2.start(); wait(); - } else if (Thread.currentThread().equals(producerThread2)){ + } else if (Thread.currentThread().equals(producerThread2)) { consumerThread.start(); wait(); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeoutEvent.java similarity index 80% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeoutEvent.java index ff367fd236fb..908a0db77b3a 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeout.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorWaitTimeoutEvent.java @@ -28,25 +28,24 @@ import static org.junit.Assert.assertTrue; -import java.util.List; - -import org.junit.Test; import org.junit.Assert; +import org.junit.Test; import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedObject; import jdk.jfr.consumer.RecordedThread; -public class TestJavaMonitorWaitTimeout extends JfrTest { +public class TestJavaMonitorWaitTimeoutEvent extends JfrTest { private static final int MILLIS = 50; - static final Helper helper = new Helper(); - static Thread unheardNotifierThread; - static Thread timeoutThread; - static Thread simpleWaitThread; - static Thread simpleNotifyThread; - private boolean timeoutFound = false; - private boolean simpleWaitFound = false; + + private final Helper helper = new Helper(); + private Thread unheardNotifierThread; + private Thread timeoutThread; + private Thread simpleWaitThread; + private Thread simpleNotifyThread; + private boolean timeoutFound; + private boolean simpleWaitFound; + @Override public String[] getTestedEvents() { return new String[]{"jdk.JavaMonitorWait"}; @@ -54,26 +53,22 @@ public String[] getTestedEvents() { @Override public void validateEvents() throws Throwable { - List events; - events = getEvents("TestJavaMonitorWaitTimeout"); - - for (RecordedEvent event : events) { - RecordedObject struct = event; - String eventThread = struct. getValue("eventThread").getJavaName(); - String notifThread = struct. getValue("notifier") != null ? struct. getValue("notifier").getJavaName() : null; + for (RecordedEvent event : getEvents()) { + String eventThread = event. getValue("eventThread").getJavaName(); + String notifThread = event. getValue("notifier") != null ? event. getValue("notifier").getJavaName() : null; if (!eventThread.equals(unheardNotifierThread.getName()) && !eventThread.equals(timeoutThread.getName()) && !eventThread.equals(simpleNotifyThread.getName()) && !eventThread.equals(simpleWaitThread.getName())) { continue; } - if (!struct. getValue("monitorClass").getName().equals(Helper.class.getName())) { + if (!event. getValue("monitorClass").getName().equals(Helper.class.getName())) { continue; } assertTrue("Event is wrong duration:" + event.getDuration().toMillis(), event.getDuration().toMillis() >= MILLIS); if (eventThread.equals(timeoutThread.getName())) { assertTrue("Notifier of timeout thread should be null", notifThread == null); - assertTrue("Should have timed out.", struct. getValue("timedOut").booleanValue()); + assertTrue("Should have timed out.", event. getValue("timedOut").booleanValue()); timeoutFound = true; } else if (eventThread.equals(simpleWaitThread.getName())) { assertTrue("Notifier of simple wait is incorrect", notifThread.equals(simpleNotifyThread.getName())); @@ -85,7 +80,7 @@ public void validateEvents() throws Throwable { simpleWaitFound && timeoutFound); } - private static void testTimeout() throws InterruptedException { + private void testTimeout() throws InterruptedException { Runnable unheardNotifier = () -> { helper.unheardNotify(); }; @@ -110,7 +105,7 @@ private static void testTimeout() throws InterruptedException { } - private static void testWaitNotify() throws Exception { + private void testWaitNotify() throws Exception { Runnable simpleWaiter = () -> { try { helper.simpleNotify(); @@ -131,6 +126,7 @@ private static void testWaitNotify() throws Exception { simpleNotifyThread = new Thread(simpleNotifier); simpleWaitThread.start(); + simpleWaitThread.join(); simpleNotifyThread.join(); } @@ -141,7 +137,7 @@ public void test() throws Exception { testWaitNotify(); } - static class Helper { + private class Helper { public synchronized void timeout() throws InterruptedException { wait(MILLIS); } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java index 12d440eec701..b768e4fd0f84 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java @@ -25,22 +25,15 @@ package com.oracle.svm.test.jfr; -import org.graalvm.word.WordFactory; import org.junit.Test; import com.oracle.svm.core.jfr.HasJfrSupport; -import com.oracle.svm.core.sampler.SamplerBuffer; -import com.oracle.svm.core.sampler.SamplerBufferAccess; -import com.oracle.svm.core.sampler.SamplerBuffersAccess; -import com.oracle.svm.core.sampler.SamplerThreadLocal; import com.oracle.svm.test.jfr.events.StackTraceEvent; /** * Test if event ({@link StackTraceEvent}) with stacktrace payload is working. */ public class TestStackTraceEvent extends JfrTest { - private static final int LOCAL_BUFFER_SIZE = 1024; - @Override public String[] getTestedEvents() { return new String[]{ @@ -66,21 +59,11 @@ public void test() throws Exception { return; } - /* Set thread-local buffer before stack walk. */ - SamplerBuffer buffer = SamplerBufferAccess.allocate(WordFactory.unsigned(LOCAL_BUFFER_SIZE)); - SamplerThreadLocal.setThreadLocalBuffer(buffer); - /* * Create and commit an event. This will trigger * com.oracle.svm.core.jfr.JfrStackTraceRepository.getStackTraceId(int) call and stack walk. */ StackTraceEvent event = new StackTraceEvent(); event.commit(); - - /* Call manually buffer processing. */ - SamplerBuffersAccess.processSamplerBuffer(buffer); - - /* We need to free memory manually as well afterward. */ - SamplerBufferAccess.free(buffer); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java index 8585ff196f73..5bf127a9c185 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStringEvent.java @@ -26,9 +26,10 @@ package com.oracle.svm.test.jfr; -import com.oracle.svm.test.jfr.events.StringEvent; import org.junit.Test; +import com.oracle.svm.test.jfr.events.StringEvent; + public class TestStringEvent extends JfrTest { @Override public String[] getTestedEvents() { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleepEvent.java similarity index 81% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleepEvent.java index e474bc1e6aeb..650886322d89 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleep.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestThreadSleepEvent.java @@ -26,37 +26,30 @@ package com.oracle.svm.test.jfr; -import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedObject; -import jdk.jfr.consumer.RecordedThread; +import static org.junit.Assert.assertTrue; import java.io.IOException; -import java.util.List; -import static org.junit.Assert.assertTrue; import org.junit.Test; -public class TestThreadSleep extends JfrTest { - private Thread sleepingThread; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedThread; + +public class TestThreadSleepEvent extends JfrTest { private static final int MILLIS = 50; + private Thread sleepingThread; + @Override public String[] getTestedEvents() { return new String[]{"jdk.ThreadSleep"}; } @Override - public void validateEvents() { - List events; - try { - events = getEvents("jdk.ThreadSleep"); - } catch (IOException e) { - throw new RuntimeException(e); - } + public void validateEvents() throws IOException { boolean foundSleepEvent = false; - for (RecordedEvent event : events) { - RecordedObject struct = event; - String eventThread = struct. getValue("eventThread").getJavaName(); + for (RecordedEvent event : getEvents()) { + String eventThread = event. getValue("eventThread").getJavaName(); if (!eventThread.equals(sleepingThread.getName())) { continue; } From 7e9ea6b23a6df4c73f3917727154b8a31a3690c0 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Mon, 7 Nov 2022 11:31:51 +0100 Subject: [PATCH 17/21] Support JFR mirror events (e.g., thread sleep event on JDK 19). --- substratevm/mx.substratevm/suite.py | 1 + .../src/com/oracle/svm/core/jfr/JfrEvent.java | 23 ++----- .../com/oracle/svm/core/jfr/JfrFeature.java | 7 +- .../oracle/svm/core/jfr/JfrJavaEvents.java | 17 +++-- .../core/jfr/Target_jdk_jfr_internal_JVM.java | 10 ++- ...pEvent.java => ThreadSleepEventJDK17.java} | 9 +-- .../oracle/svm/core/thread/JavaThreads.java | 46 +++++++++++-- .../svm/hosted/jfr/JfrEventFeature.java | 36 +++++----- .../svm/hosted/jfr/JfrEventSubstitution.java | 67 +++++++++++++++---- .../src/com/oracle/svm/test/jfr/JfrTest.java | 30 +++------ 10 files changed, 155 insertions(+), 91 deletions(-) rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/{ThreadSleepEvent.java => ThreadSleepEventJDK17.java} (91%) diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index f94776e050b4..6975b1a339bb 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -566,6 +566,7 @@ ], "requiresConcealed" : { "java.base" : [ + "jdk.internal.event", "jdk.internal.misc", "jdk.internal.vm.annotation", "jdk.internal.org.objectweb.asm", diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java index 3635233cfd5c..01fb89611283 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java @@ -24,18 +24,14 @@ */ package com.oracle.svm.core.jfr; -import java.util.function.BooleanSupplier; - import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.jdk.JDK17OrEarlier; -import com.oracle.svm.util.ReflectionUtil; /** - * The event IDs depend on the metadata.xml and therefore vary between JDK versions. + * This file contains the VM-level events that Native Image supports on all JDK versions. The event + * IDs depend on the JDK version-specific metadata.xml file. */ public final class JfrEvent { public static final JfrEvent ThreadStart = create("jdk.ThreadStart"); @@ -60,7 +56,6 @@ public final class JfrEvent { public static final JfrEvent SafepointEnd = create("jdk.SafepointEnd"); public static final JfrEvent ExecuteVMOperation = create("jdk.ExecuteVMOperation"); public static final JfrEvent JavaMonitorEnter = create("jdk.JavaMonitorEnter"); - public static final JfrEvent ThreadSleep = create("jdk.ThreadSleep", JDK17OrEarlier.class); public static final JfrEvent ThreadPark = create("jdk.ThreadPark"); public static final JfrEvent JavaMonitorWait = create("jdk.JavaMonitorWait"); @@ -68,18 +63,8 @@ public final class JfrEvent { private final String name; @Platforms(Platform.HOSTED_ONLY.class) - private static JfrEvent create(String name) { - return create(name, TargetClass.AlwaysIncluded.class); - } - - @Platforms(Platform.HOSTED_ONLY.class) - private static JfrEvent create(String name, Class onlyWith) { - BooleanSupplier onlyWithProvider = ReflectionUtil.newInstance(onlyWith); - if (onlyWithProvider.getAsBoolean()) { - return new JfrEvent(name); - } else { - return null; - } + public static JfrEvent create(String name) { + return new JfrEvent(name); } @Platforms(Platform.HOSTED_ONLY.class) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java index 405cc7027057..caba4c62be17 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java @@ -49,7 +49,6 @@ import com.sun.management.internal.PlatformMBeanProviderImpl; import jdk.jfr.Configuration; -import jdk.jfr.Event; import jdk.jfr.internal.JVM; import jdk.jfr.internal.jfc.JFC; @@ -59,9 +58,9 @@ * * There are two different kinds of JFR events: *
    - *
  • Java-level events are defined by a Java class that extends {@link Event} and that is - * annotated with JFR-specific annotations. Those events are typically triggered by the Java - * application and a Java {@code EventWriter} object is used when writing the event to a + *
  • Java-level events are defined by a Java class that extends {@link jdk.internal.event.Event} + * and that is annotated with JFR-specific annotations. Those events are typically triggered by the + * Java application and a Java {@code EventWriter} object is used when writing the event to a * buffer.
  • *
  • Native events are triggered by the JVM itself and are defined in the JFR metadata.xml file. * For writing such an event to a buffer, we call into {@link JfrNativeEventWriter} and pass a diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrJavaEvents.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrJavaEvents.java index d0dabe6c6816..d62c88d9468e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrJavaEvents.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrJavaEvents.java @@ -26,7 +26,7 @@ import java.util.ArrayList; import java.util.List; -import jdk.jfr.Event; + import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -34,14 +34,23 @@ * Holds all JFR Java-level event classes. */ public class JfrJavaEvents { - private static final List> EVENT_CLASSES = new ArrayList<>(); + private static final List> EVENT_CLASSES = new ArrayList<>(); + private static final List> JFR_EVENT_CLASSES = new ArrayList<>(); @Platforms(Platform.HOSTED_ONLY.class) - public static void registerEventClass(Class eventClass) { + @SuppressWarnings("unchecked") + public static void registerEventClass(Class eventClass) { EVENT_CLASSES.add(eventClass); + if (jdk.jfr.Event.class.isAssignableFrom(eventClass)) { + JFR_EVENT_CLASSES.add((Class) eventClass); + } } - public static List> getAllEventClasses() { + public static List> getAllEventClasses() { return EVENT_CLASSES; } + + public static List> getJfrEventClasses() { + return JFR_EVENT_CLASSES; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java index 6d5d9963e772..d512844793f3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/Target_jdk_jfr_internal_JVM.java @@ -97,10 +97,18 @@ public boolean isRecording() { /** See {@link JVM#getAllEventClasses}. */ @Substitute - public List> getAllEventClasses() { + @TargetElement(onlyWith = JDK17OrLater.class) + public List> getAllEventClasses() { return JfrJavaEvents.getAllEventClasses(); } + /** See {@link JVM#getAllEventClasses}. */ + @Substitute + @TargetElement(name = "getAllEventClasses", onlyWith = JDK11OrEarlier.class) + public List> getAllEventClassesJDK11() { + return JfrJavaEvents.getJfrEventClasses(); + } + /** See {@link JVM#getUnloadedEventClassCount}. */ @Substitute public long getUnloadedEventClassCount() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEventJDK17.java similarity index 91% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEvent.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEventJDK17.java index 2c779b5db58a..260124700ccf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadSleepEventJDK17.java @@ -36,7 +36,8 @@ import com.oracle.svm.core.jfr.JfrTicks; import com.oracle.svm.core.jfr.SubstrateJVM; -public class ThreadSleepEvent { +public class ThreadSleepEventJDK17 { + private static final JfrEvent ThreadSleep = JfrEvent.create("jdk.ThreadSleep"); public static void emit(long time, long startTicks) { if (com.oracle.svm.core.jfr.HasJfrSupport.get()) { @@ -46,15 +47,15 @@ public static void emit(long time, long startTicks) { @Uninterruptible(reason = "Accesses a JFR buffer.") private static void emit0(long time, long startTicks) { - if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(JfrEvent.ThreadSleep)) { + if (SubstrateJVM.isRecording() && SubstrateJVM.get().isEnabled(ThreadSleep)) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); - JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ThreadSleep); + JfrNativeEventWriter.beginSmallEvent(data, ThreadSleep); JfrNativeEventWriter.putLong(data, startTicks); JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks() - startTicks); JfrNativeEventWriter.putEventThread(data); - JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ThreadSleep, 0)); + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(ThreadSleep, 0)); JfrNativeEventWriter.putLong(data, time); JfrNativeEventWriter.endSmallEvent(data); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java index 2c0f772b9fab..d634a359649c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java @@ -27,6 +27,7 @@ import java.lang.Thread.UncaughtExceptionHandler; import java.security.AccessControlContext; import java.security.AccessController; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -36,11 +37,14 @@ import org.graalvm.nativeimage.IsolateThread; import org.graalvm.word.Pointer; -import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.jfr.events.ThreadSleepEvent; +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.jdk.JDK19OrLater; +import com.oracle.svm.core.jfr.events.ThreadSleepEventJDK17; import com.oracle.svm.core.snippets.KnownIntrinsics; import com.oracle.svm.util.ReflectionUtil; @@ -342,13 +346,33 @@ static void initNewThreadLocalsAndLoader(Target_java_lang_Thread tjlt, boolean a } static void sleep(long millis) throws InterruptedException { - long startTicks = com.oracle.svm.core.jfr.JfrTicks.elapsedTicks(); + /* Starting with JDK 19, the thread sleep event is implemented as a Java-level event. */ + if (JavaVersionUtil.JAVA_SPEC >= 19) { + if (com.oracle.svm.core.jfr.HasJfrSupport.get() && Target_jdk_internal_event_ThreadSleepEvent.isTurnedOn()) { + Target_jdk_internal_event_ThreadSleepEvent event = new Target_jdk_internal_event_ThreadSleepEvent(); + try { + event.time = TimeUnit.MILLISECONDS.toNanos(millis); + event.begin(); + sleep0(millis); + } finally { + event.commit(); + } + } else { + sleep0(millis); + } + } else { + long startTicks = com.oracle.svm.core.jfr.JfrTicks.elapsedTicks(); + sleep0(millis); + ThreadSleepEventJDK17.emit(millis, startTicks); + } + } + + private static void sleep0(long millis) throws InterruptedException { if (supportsVirtual() && isVirtualDisallowLoom(Thread.currentThread()) && !LoomSupport.isEnabled()) { VirtualThreads.singleton().sleepMillis(millis); } else { PlatformThreads.sleep(millis); } - ThreadSleepEvent.emit(millis, startTicks); } static boolean isAlive(Thread thread) { @@ -384,3 +408,17 @@ public static void blockedOn(Target_sun_nio_ch_Interruptible b) { } } } + +@TargetClass(className = "jdk.internal.event.ThreadSleepEvent", onlyWith = JDK19OrLater.class) +final class Target_jdk_internal_event_ThreadSleepEvent { + @Alias public long time; + + @Alias + public static native boolean isTurnedOn(); + + @Alias + public native void begin(); + + @Alias + public native void commit(); +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java index aa3d7984ea13..fbccdebb2bdd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventFeature.java @@ -50,8 +50,9 @@ import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.util.ModuleSupport; +import com.oracle.svm.util.ReflectionUtil; -import jdk.jfr.Event; +import jdk.internal.event.Event; import jdk.jfr.internal.JVM; import jdk.vm.ci.meta.MetaAccessProvider; @@ -76,6 +77,7 @@ public void afterRegistration(AfterRegistrationAccess access) { ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrEventFeature.class, false, "jdk.jfr", "jdk.jfr.events"); ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrFeature.class, false, "jdk.jfr", "jdk.jfr.events"); ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrEventSubstitution.class, false, "jdk.internal.vm.ci", "jdk.vm.ci.hotspot"); + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrEventFeature.class, false, "java.base", "jdk.internal.event"); } @Override @@ -92,10 +94,8 @@ public void duringSetup(DuringSetupAccess c) { @Override public void beforeAnalysis(Feature.BeforeAnalysisAccess access) { if (JavaVersionUtil.JAVA_SPEC < 19) { - Class eventClass = access.findClassByName("jdk.internal.event.Event"); - if (eventClass != null) { - access.registerSubtypeReachabilityHandler(JfrEventFeature::eventSubtypeReachable, eventClass); - } + /* In JDK 19+, events don't have an eventHandler field anymore. */ + access.registerSubtypeReachabilityHandler(JfrEventFeature::eventSubtypeReachable, Event.class); } } @@ -115,12 +115,13 @@ public void beforeCompilation(BeforeCompilationAccess a) { // Off-set by one for error-catcher JfrTraceId.assign(clazz, hub.getTypeID() + 1); } + + /* Store the event configuration in the dynamic hub companion. */ if (JavaVersionUtil.JAVA_SPEC >= 19) { try { FeatureImpl.CompilationAccessImpl accessImpl = ((FeatureImpl.CompilationAccessImpl) a); Method getConfiguration = JVM.class.getDeclaredMethod("getConfiguration", Class.class); for (var newEventClass : JfrJavaEvents.getAllEventClasses()) { - /* Store the event configuration in the companion. */ Object ec = getConfiguration.invoke(JVM.getJVM(), newEventClass); DynamicHub dynamicHub = accessImpl.getMetaAccess().lookupJavaType(newEventClass).getHub(); dynamicHub.setJrfEventConfiguration(ec); @@ -132,23 +133,16 @@ public void beforeCompilation(BeforeCompilationAccess a) { } private static void eventSubtypeReachable(DuringAnalysisAccess a, Class c) { - DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; - if (c.getCanonicalName().equals("jdk.jfr.Event") || - c.getCanonicalName().equals("jdk.internal.event.Event") || - c.getCanonicalName().equals("jdk.jfr.events.AbstractJDKEvent") || - c.getCanonicalName().equals("jdk.jfr.events.AbstractBufferStatisticsEvent") || - Modifier.isAbstract(c.getModifiers())) { + if (Modifier.isAbstract(c.getModifiers())) { return; } - try { - Field f = c.getDeclaredField("eventHandler"); - RuntimeReflection.register(f); - access.rescanRoot(f); - if (!access.concurrentReachabilityHandlers()) { - access.requireAnalysisIteration(); - } - } catch (Exception e) { - throw VMError.shouldNotReachHere("Unable to register eventHandler for: " + c.getCanonicalName(), e); + + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + Field f = ReflectionUtil.lookupField(c, "eventHandler"); + RuntimeReflection.register(f); + access.rescanRoot(f); + if (!access.concurrentReachabilityHandlers()) { + access.requireAnalysisIteration(); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java index 7bf6e1702a4f..05d6d47bb659 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java @@ -24,11 +24,15 @@ */ package com.oracle.svm.hosted.jfr; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.concurrent.ConcurrentHashMap; +import org.graalvm.collections.EconomicMap; +import org.graalvm.compiler.serviceprovider.JavaVersionUtil; +import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -38,9 +42,9 @@ import com.oracle.svm.core.jfr.JfrEventWriterAccess; import com.oracle.svm.core.jfr.JfrJavaEvents; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ReflectionUtil; import jdk.internal.misc.Unsafe; -import jdk.jfr.Event; import jdk.jfr.internal.JVM; import jdk.jfr.internal.SecuritySupport; import jdk.vm.ci.meta.MetaAccessProvider; @@ -50,31 +54,33 @@ import jdk.vm.ci.meta.Signature; /** - * This class triggers the class redefinition (see {@link JVM#retransformClasses}) for all - * {@link Event} classes that are visited during static analysis. + * This class triggers the class redefinition (see {@link JVM#retransformClasses}) for all event + * classes that are visited during static analysis. */ @Platforms(Platform.HOSTED_ONLY.class) public class JfrEventSubstitution extends SubstitutionProcessor { - private final ResolvedJavaType jdkJfrEvent; + private final ResolvedJavaType eventType; private final ConcurrentHashMap typeSubstitution; private final ConcurrentHashMap methodSubstitutions; private final ConcurrentHashMap fieldSubstitutions; + private final EconomicMap> mirrorEventMapping; JfrEventSubstitution(MetaAccessProvider metaAccess) { - jdkJfrEvent = metaAccess.lookupJavaType(Event.class); + eventType = metaAccess.lookupJavaType(jdk.internal.event.Event.class); ResolvedJavaType jdkJfrEventWriter = metaAccess.lookupJavaType(JfrEventWriterAccess.getEventWriterClass()); changeWriterResetMethod(jdkJfrEventWriter); typeSubstitution = new ConcurrentHashMap<>(); methodSubstitutions = new ConcurrentHashMap<>(); fieldSubstitutions = new ConcurrentHashMap<>(); + mirrorEventMapping = createMirrorEventsMapping(); } @Override public ResolvedJavaField lookup(ResolvedJavaField field) { ResolvedJavaType type = field.getDeclaringClass(); if (needsClassRedefinition(type)) { - typeSubstitution.computeIfAbsent(type, JfrEventSubstitution::initEventClass); + typeSubstitution.computeIfAbsent(type, this::initEventClass); return fieldSubstitutions.computeIfAbsent(field, JfrEventSubstitution::initEventField); } return field; @@ -84,7 +90,7 @@ public ResolvedJavaField lookup(ResolvedJavaField field) { public ResolvedJavaMethod lookup(ResolvedJavaMethod method) { ResolvedJavaType type = method.getDeclaringClass(); if (needsClassRedefinition(type)) { - typeSubstitution.computeIfAbsent(type, JfrEventSubstitution::initEventClass); + typeSubstitution.computeIfAbsent(type, this::initEventClass); return methodSubstitutions.computeIfAbsent(method, JfrEventSubstitution::initEventMethod); } return method; @@ -93,7 +99,7 @@ public ResolvedJavaMethod lookup(ResolvedJavaMethod method) { @Override public ResolvedJavaType lookup(ResolvedJavaType type) { if (needsClassRedefinition(type)) { - typeSubstitution.computeIfAbsent(type, JfrEventSubstitution::initEventClass); + typeSubstitution.computeIfAbsent(type, this::initEventClass); } return type; } @@ -139,14 +145,24 @@ private static ResolvedJavaMethod initEventMethod(ResolvedJavaMethod oldMethod) throw VMError.shouldNotReachHere("Could not re-resolve method: " + oldMethod); } - private static Boolean initEventClass(ResolvedJavaType eventType) throws RuntimeException { + @SuppressWarnings("unchecked") + private Boolean initEventClass(ResolvedJavaType eventType) throws RuntimeException { try { - Class newEventClass = OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), eventType).asSubclass(Event.class); + Class newEventClass = OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), eventType) + .asSubclass(jdk.internal.event.Event.class); eventType.initialize(); + + // It is crucial that mirror event are registered before the actual events. + Class mirrorEventClass = mirrorEventMapping.get(newEventClass.getName()); + if (mirrorEventClass != null) { + SecuritySupport.registerMirror(mirrorEventClass); + } + SecuritySupport.registerEvent(newEventClass); + JfrJavaEvents.registerEventClass(newEventClass); // the reflection registration for the event handler field is delayed to the JfrFeature - // duringAnalysis callback so it does not not race/interfere with other retransforms + // duringAnalysis callback so it does not race/interfere with other retransforms JVM.getJVM().retransformClasses(new Class[]{newEventClass}); return Boolean.TRUE; } catch (Throwable ex) { @@ -155,7 +171,7 @@ private static Boolean initEventClass(ResolvedJavaType eventType) throws Runtime } private boolean needsClassRedefinition(ResolvedJavaType type) { - return !type.isAbstract() && jdkJfrEvent.isAssignableFrom(type) && !jdkJfrEvent.equals(type); + return !type.isAbstract() && eventType.isAssignableFrom(type) && !eventType.equals(type); } /** @@ -205,4 +221,31 @@ private static Method getMethodToFetchMetaspaceMethod(Class method) throws No } } } + + /* + * Mirror events contain the JFR-specific annotations. The mirrored event does not have any + * dependency on JFR-specific classes. If the mirrored event is used, we must ensure that the + * mirror event is registered as well. Otherwise, incorrect JFR metadata would be emitted. + */ + @SuppressWarnings("unchecked") + private static EconomicMap> createMirrorEventsMapping() { + EconomicMap> result = EconomicMap.create(); + if (JavaVersionUtil.JAVA_SPEC >= 17) { + Class mirrorEventAnnotationClass = (Class) ReflectionUtil.lookupClass(false, "jdk.jfr.internal.MirrorEvent"); + Class jdkEventsClass = ReflectionUtil.lookupClass(false, "jdk.jfr.internal.instrument.JDKEvents"); + Class[] mirrorEventClasses = ReflectionUtil.readStaticField(jdkEventsClass, "mirrorEventClasses"); + for (int i = 0; i < mirrorEventClasses.length; i++) { + Class mirrorEventClass = (Class) mirrorEventClasses[i]; + Annotation mirrorEvent = AnnotationAccess.getAnnotation(mirrorEventClass, mirrorEventAnnotationClass); + Method m = ReflectionUtil.lookupMethod(mirrorEventAnnotationClass, "className"); + try { + String className = (String) m.invoke(mirrorEvent); + result.put(className, mirrorEventClass); + } catch (Exception e) { + throw VMError.shouldNotReachHere(e); + } + } + } + return result; + } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java index 0e4aff2e6002..f1723b64617f 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java @@ -37,7 +37,6 @@ import java.util.HashSet; import java.util.List; -import com.oracle.svm.util.ClassUtil; import org.graalvm.nativeimage.ImageInfo; import org.graalvm.nativeimage.hosted.Feature; import org.junit.After; @@ -49,6 +48,7 @@ import com.oracle.svm.test.jfr.utils.Jfr; import com.oracle.svm.test.jfr.utils.JfrFileParser; import com.oracle.svm.test.jfr.utils.LocalJfr; +import com.oracle.svm.util.ClassUtil; import com.oracle.svm.util.ModuleSupport; import jdk.jfr.Recording; @@ -66,35 +66,21 @@ public static void checkForJFR() { } @Before - public void startRecording() { - try { - jfr = new LocalJfr(); - recording = jfr.createRecording(getClass().getName()); - enableEvents(); - jfr.startRecording(recording); - } catch (Exception e) { - Assert.fail("Fail to start recording! Cause: " + e.getMessage()); - } + public void startRecording() throws Throwable { + jfr = new LocalJfr(); + recording = jfr.createRecording(getClass().getName()); + enableEvents(); + jfr.startRecording(recording); } @After - public void endRecording() { + public void endRecording() throws Throwable { try { jfr.endRecording(recording); checkRecording(); - } catch (Exception e) { - Assert.fail("Fail to stop recording! Cause: " + e.getMessage()); - } - try { validateEvents(); - } catch (Throwable throwable) { - Assert.fail("validateEvents failed: " + throwable.getMessage()); } finally { - try { - jfr.cleanupRecording(recording); - } catch (Exception e) { - Assert.fail("Fail to cleanup recording! Cause: " + e.getMessage()); - } + jfr.cleanupRecording(recording); } } From c1d3b00256cef8ab01c9ca9ed81f6a5c231a3640 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Thu, 17 Nov 2022 11:28:36 +0100 Subject: [PATCH 18/21] Various fixes for JFR stack traces. --- .../oracle/svm/core/jfr/JfrChunkWriter.java | 3 +- .../svm/core/jfr/JfrRecorderThread.java | 2 +- .../svm/core/jfr/JfrStackTraceRepository.java | 2 +- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 9 ++- .../svm/core/sampler/SamplerBufferPool.java | 38 +++++++---- .../core/sampler/SamplerBuffersAccess.java | 66 +++++++++++-------- .../SamplerSampleWriterDataAccess.java | 14 ++-- .../svm/core/sampler/SamplerThreadLocal.java | 9 ++- .../core/sampler/SubstrateSigprofHandler.java | 4 +- .../svm/hosted/jfr/JfrEventSubstitution.java | 7 +- .../src/com/oracle/svm/test/jfr/JfrTest.java | 10 --- .../svm/test/jfr/TestStackTraceEvent.java | 17 ----- 12 files changed, 99 insertions(+), 82 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java index e66728803603..16e973b5de26 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java @@ -385,7 +385,8 @@ protected void operate() { @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") private void changeEpoch() { /* Process all unprocessed sampler buffers before changing the epoch. */ - SamplerBuffersAccess.processSamplerBuffers(); + SamplerBuffersAccess.processActiveBuffers(); + SamplerBuffersAccess.processFullBuffers(); // Write unflushed data from the thread local buffers but do *not* reinitialize them // The thread local code will handle space reclamation on their own time diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java index cfd5f8a3a6e8..84e31e4cde57 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrRecorderThread.java @@ -116,7 +116,7 @@ private boolean await() { private void run0() { /* Process all unprocessed sampler buffers. */ - SamplerBuffersAccess.processSamplerBuffers(); + SamplerBuffersAccess.processFullBuffers(); JfrChunkWriter chunkWriter = unlockedChunkWriter.lock(); try { if (chunkWriter.hasOpenFile()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java index 27725464a2ba..fb7c9d67b7e2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java @@ -107,7 +107,7 @@ public long getStackTraceId(int skipCount) { /* Initialize stack walk. */ SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class); SamplerThreadLocal.setSignalHandlerLocallyDisabled(true); - if (SamplerSampleWriterDataAccess.initialize(data, skipCount, maxDepth)) { + if (SamplerSampleWriterDataAccess.initialize(data, skipCount, maxDepth, true)) { SamplerSampleWriter.begin(data); /* Walk the stack. */ Pointer sp = KnownIntrinsics.readCallerStackPointer(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java index d1ebc70da7fb..473d98277d23 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java @@ -292,7 +292,9 @@ public void storeMetadataDescriptor(byte[] bytes) { /** See {@link JVM#beginRecording}. */ public void beginRecording() { - assert !recording; + if (recording) { + return; + } JfrChunkWriter chunkWriter = unlockedChunkWriter.lock(); try { @@ -309,7 +311,10 @@ public void beginRecording() { /** See {@link JVM#endRecording}. */ public void endRecording() { - assert recording; + if (!recording) { + return; + } + JfrEndRecordingOperation vmOp = new JfrEndRecordingOperation(); vmOp.enqueue(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java index 45df346c78c9..de9be373c545 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferPool.java @@ -42,17 +42,27 @@ class SamplerBufferPool { private static final VMMutex mutex = new VMMutex("SamplerBufferPool"); private static long bufferCount; - @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true) + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void releaseBufferAndAdjustCount(SamplerBuffer threadLocalBuffer) { adjustBufferCount0(threadLocalBuffer); } - @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true) + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void adjustBufferCount() { adjustBufferCount0(WordFactory.nullPointer()); } - @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true) + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") + public static SamplerBuffer tryAllocateBuffer() { + mutex.lockNoTransition(); + try { + return tryAllocateBuffer0(); + } finally { + mutex.unlock(); + } + } + + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") private static void adjustBufferCount0(SamplerBuffer threadLocalBuffer) { mutex.lockNoTransition(); try { @@ -76,7 +86,7 @@ private static void adjustBufferCount0(SamplerBuffer threadLocalBuffer) { } } - @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.", mayBeInlined = true) + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static void releaseThreadLocalBuffer(SamplerBuffer buffer) { /* * buffer is null if the thread is not running yet, or we did not perform the stack walk for @@ -99,15 +109,22 @@ private static void releaseThreadLocalBuffer(SamplerBuffer buffer) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static boolean allocateAndPush() { VMError.guarantee(bufferCount >= 0); - JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); - SamplerBuffer buffer = SamplerBufferAccess.allocate(WordFactory.unsigned(jfrThreadLocal.getThreadLocalBufferSize())); + SamplerBuffer buffer = tryAllocateBuffer0(); if (buffer.isNonNull()) { SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer); - bufferCount++; return true; - } else { - return false; } + return false; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static SamplerBuffer tryAllocateBuffer0() { + JfrThreadLocal jfrThreadLocal = (JfrThreadLocal) SubstrateJVM.getThreadLocal(); + SamplerBuffer result = SamplerBufferAccess.allocate(WordFactory.unsigned(jfrThreadLocal.getThreadLocalBufferSize())); + if (result.isNonNull()) { + bufferCount++; + } + return result; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -118,9 +135,8 @@ private static boolean popAndFree() { SamplerBufferAccess.free(buffer); bufferCount--; return true; - } else { - return false; } + return false; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java index 4f271398f32b..5ae8be175b3d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java @@ -25,6 +25,7 @@ package com.oracle.svm.core.sampler; +import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.c.type.CIntPointer; @@ -38,10 +39,11 @@ import com.oracle.svm.core.code.CodeInfoTable; import com.oracle.svm.core.code.FrameInfoQueryResult; import com.oracle.svm.core.code.UntetheredCodeInfo; -import com.oracle.svm.core.jfr.HasJfrSupport; import com.oracle.svm.core.jfr.JfrStackTraceRepository; import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.jfr.events.ExecutionSampleEvent; +import com.oracle.svm.core.thread.VMOperation; +import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.util.VMError; /** @@ -54,43 +56,51 @@ private SamplerBuffersAccess() { } @Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.") - public static void processSamplerBuffers() { - if (!HasJfrSupport.get()) { - /* - * This method will become reachable on WINDOWS during the building of JFR tests via - * com.oracle.svm.core.jfr.JfrChunkWriter.closeFile, and it will fail during - * InvocationPlugin call if we do not have this check. - * - * Note that although we are building the JFR tests for Windows as well, they will not - * be executed because of guard in com.oracle.svm.test.jfr.JfrTest.checkForJFR. - * - * Once we have support for Windows, this check will become obsolete. - */ - return; - } + public static void processActiveBuffers() { + VMOperation.guaranteeInProgressAtSafepoint("Needed for iterating the threads"); SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(true); - while (true) { - /* Pop top buffer from stack of full buffers. */ - SamplerBuffer buffer = SubstrateSigprofHandler.singleton().fullBuffers().popBuffer(); - if (buffer.isNull()) { - /* No buffers to process. */ - SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(false); - return; + try { + for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { + SamplerBuffer buffer = SamplerThreadLocal.getThreadLocalBuffer(thread); + if (buffer.isNonNull()) { + processSamplerBuffer(buffer); + } } + } finally { + SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(false); + } + } - /* Process the buffer. */ - processSamplerBuffer(buffer); - if (buffer.getFreeable()) { - SamplerBufferAccess.free(buffer); - } else { - SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer); + @Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.") + public static void processFullBuffers() { + SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(true); + try { + while (true) { + /* Pop top buffer from stack of full buffers. */ + SamplerBuffer buffer = SubstrateSigprofHandler.singleton().fullBuffers().popBuffer(); + if (buffer.isNull()) { + /* No remaining buffers. */ + break; + } + + /* Process the buffer. */ + processSamplerBuffer(buffer); + if (buffer.getFreeable()) { + SamplerBufferAccess.free(buffer); + } else { + SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer); + } } + } finally { + SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(false); } } @Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.") public static void processSamplerBuffer(SamplerBuffer buffer) { + assert buffer.isNonNull(); + Pointer end = buffer.getPos(); Pointer current = SamplerBufferAccess.getDataStart(buffer); while (current.belowThan(end)) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java index 495773aa96ac..9e0ea6b07978 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerSampleWriterDataAccess.java @@ -40,15 +40,21 @@ private SamplerSampleWriterDataAccess() { * native buffer. */ @Uninterruptible(reason = "Accesses a sampler buffer", callerMustBe = true) - public static boolean initialize(SamplerSampleWriterData data, int skipCount, int maxDepth) { + public static boolean initialize(SamplerSampleWriterData data, int skipCount, int maxDepth, boolean allowBufferAllocation) { SamplerBuffer buffer = SamplerThreadLocal.getThreadLocalBuffer(); if (buffer.isNull()) { /* Pop first free buffer from the pool. */ buffer = SubstrateSigprofHandler.singleton().availableBuffers().popBuffer(); if (buffer.isNull()) { - /* No available buffers on the pool. Fallback! */ - SamplerThreadLocal.increaseMissedSamples(); - return false; + if (allowBufferAllocation) { + buffer = SamplerBufferPool.tryAllocateBuffer(); + } + + if (buffer.isNull()) { + /* No available buffers on the pool. Fallback! */ + SamplerThreadLocal.increaseMissedSamples(); + return false; + } } SamplerThreadLocal.setThreadLocalBuffer(buffer); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java index 1819cabab3b2..1f29bc7e1b23 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java @@ -25,6 +25,7 @@ package com.oracle.svm.core.sampler; +import com.oracle.svm.core.thread.VMOperation; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.word.UnsignedWord; @@ -102,7 +103,13 @@ static void teardown(IsolateThread isolateThread) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static SamplerBuffer getThreadLocalBuffer() { - return localBuffer.get(); + return getThreadLocalBuffer(CurrentIsolate.getCurrentThread()); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static SamplerBuffer getThreadLocalBuffer(IsolateThread thread) { + assert CurrentIsolate.getCurrentThread() == thread || VMOperation.isInProgressAtSafepoint(); + return localBuffer.get(thread); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java index aa2efa86f2e9..6f87ad553788 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java @@ -334,7 +334,7 @@ protected static void doUninterruptibleStackWalk(RegisterDumper.Context uContext /* Initialize stack walk. */ SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class); /* Buffer size constrains stack walk size. */ - if (SamplerSampleWriterDataAccess.initialize(data, 0, Integer.MAX_VALUE)) { + if (SamplerSampleWriterDataAccess.initialize(data, 0, Integer.MAX_VALUE, false)) { SamplerSampleWriter.begin(data); /* * Walk the stack. @@ -399,7 +399,7 @@ protected void operate() { @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") private void initialize() { /* - * Iterate all over all thread and initialize the thread-local storage of each thread. + * Iterate over all threads and initialize the thread-local storage of each thread. */ for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { SamplerThreadLocal.initialize(thread); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java index 05d6d47bb659..4697eaa9924c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jfr/JfrEventSubstitution.java @@ -60,14 +60,14 @@ @Platforms(Platform.HOSTED_ONLY.class) public class JfrEventSubstitution extends SubstitutionProcessor { - private final ResolvedJavaType eventType; + private final ResolvedJavaType baseEventType; private final ConcurrentHashMap typeSubstitution; private final ConcurrentHashMap methodSubstitutions; private final ConcurrentHashMap fieldSubstitutions; private final EconomicMap> mirrorEventMapping; JfrEventSubstitution(MetaAccessProvider metaAccess) { - eventType = metaAccess.lookupJavaType(jdk.internal.event.Event.class); + baseEventType = metaAccess.lookupJavaType(jdk.internal.event.Event.class); ResolvedJavaType jdkJfrEventWriter = metaAccess.lookupJavaType(JfrEventWriterAccess.getEventWriterClass()); changeWriterResetMethod(jdkJfrEventWriter); typeSubstitution = new ConcurrentHashMap<>(); @@ -145,7 +145,6 @@ private static ResolvedJavaMethod initEventMethod(ResolvedJavaMethod oldMethod) throw VMError.shouldNotReachHere("Could not re-resolve method: " + oldMethod); } - @SuppressWarnings("unchecked") private Boolean initEventClass(ResolvedJavaType eventType) throws RuntimeException { try { Class newEventClass = OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), eventType) @@ -171,7 +170,7 @@ private Boolean initEventClass(ResolvedJavaType eventType) throws RuntimeExcepti } private boolean needsClassRedefinition(ResolvedJavaType type) { - return !type.isAbstract() && eventType.isAssignableFrom(type) && !eventType.equals(type); + return !type.isAbstract() && baseEventType.isAssignableFrom(type) && !baseEventType.equals(type); } /** diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java index f1723b64617f..b6f2de9ffb23 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java @@ -163,15 +163,5 @@ public void afterRegistration(AfterRegistrationAccess access) { * com.oracle.svm.test.jfr.utils.poolparsers.ClassConstantPoolParser.parse */ ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrTestFeature.class, false, "jdk.internal.vm.compiler", "org.graalvm.compiler.serviceprovider"); - - /* - * Use of com.oracle.svm.core.sampler.SamplerBuffer, - * com.oracle.svm.core.sampler.SamplerBufferAccess.allocate, - * com.oracle.svm.core.sampler.SamplerBufferAccess.free, - * com.oracle.svm.core.sampler.SamplerBuffersAccess.processSamplerBuffer and - * com.oracle.svm.core.sampler.SamplerThreadLocal.setThreadLocalBuffer in - * com.oracle.svm.test.jfr.TestStackTraceEvent.test. - */ - ModuleSupport.accessPackagesToClass(ModuleSupport.Access.OPEN, JfrTestFeature.class, false, "org.graalvm.nativeimage.builder", "com.oracle.svm.core.sampler"); } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java index b768e4fd0f84..5c6e64027120 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestStackTraceEvent.java @@ -27,7 +27,6 @@ import org.junit.Test; -import com.oracle.svm.core.jfr.HasJfrSupport; import com.oracle.svm.test.jfr.events.StackTraceEvent; /** @@ -43,22 +42,6 @@ public String[] getTestedEvents() { @Test public void test() throws Exception { - if (!HasJfrSupport.get()) { - /* - * The static analysis will find reachable the com.oracle.svm.core.jfr.SubstrateJVM via - * processSamplerBuffer call. Since we are not supporting JFR on Windows yet, JfrFeature - * will not add the SubstrateJVM to the list of all image singletons and therefore - * InvocationPlugin will throw an exception while folding the SubstrateJVM (see - * SubstrateJVM.get). - * - * Note that although we are building this JFR test for Windows as well, it will not be - * executed because of guard in com.oracle.svm.test.jfr.JfrTest.checkForJFR. - * - * Once we have support for Windows, this check will become obsolete. - */ - return; - } - /* * Create and commit an event. This will trigger * com.oracle.svm.core.jfr.JfrStackTraceRepository.getStackTraceId(int) call and stack walk. From ae84ff87161759e46712d21695325b3d7629647b Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Mon, 21 Nov 2022 14:33:17 +0100 Subject: [PATCH 19/21] Further fixes. --- .../oracle/svm/core/jfr/JfrChunkWriter.java | 23 ++- .../svm/core/jfr/JfrStackTraceRepository.java | 37 ++-- .../svm/core/sampler/SamplerBufferAccess.java | 10 +- .../core/sampler/SamplerBuffersAccess.java | 57 +++---- .../svm/core/sampler/SamplerThreadLocal.java | 19 ++- .../core/sampler/SubstrateSigprofHandler.java | 159 ++++++++++-------- 6 files changed, 176 insertions(+), 129 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java index 16e973b5de26..fd137a3dd331 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java @@ -41,6 +41,7 @@ import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; import com.oracle.svm.core.os.RawFileOperationSupport; import com.oracle.svm.core.sampler.SamplerBuffersAccess; +import com.oracle.svm.core.sampler.SubstrateSigprofHandler; import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMOperationControl; @@ -384,9 +385,7 @@ protected void operate() { */ @Uninterruptible(reason = "Prevent pollution of the current thread's thread local JFR buffer.") private void changeEpoch() { - /* Process all unprocessed sampler buffers before changing the epoch. */ - SamplerBuffersAccess.processActiveBuffers(); - SamplerBuffersAccess.processFullBuffers(); + processSamplerBuffers(); // Write unflushed data from the thread local buffers but do *not* reinitialize them // The thread local code will handle space reclamation on their own time @@ -414,6 +413,24 @@ private void changeEpoch() { // Now that the epoch changed, re-register all running threads for the new epoch. SubstrateJVM.getThreadRepo().registerRunningThreads(); } + + /** + * The VM is at a safepoint, so all other threads have a native state. However, the SIGPROF + * handler may still be executed at any time for any thread (including the current thread). + * To prevent races, we need to ensure that there are no threads that execute the SIGPROF + * handler while we are accessing the currently active buffers of other threads. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private void processSamplerBuffers() { + SubstrateSigprofHandler.singleton().preventThreadsFromEnteringSigProfHandler(); + try { + SubstrateSigprofHandler.singleton().waitUntilAllThreadsExitedSignalHandler(); + SamplerBuffersAccess.processActiveBuffers(); + SamplerBuffersAccess.processFullBuffers(); + } finally { + SubstrateSigprofHandler.singleton().allowThreadsInSigProfHandler(); + } + } } public long getChunkStartNanos() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java index fb7c9d67b7e2..6780c1e26a43 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrStackTraceRepository.java @@ -106,27 +106,30 @@ public long getStackTraceId(int skipCount) { /* Initialize stack walk. */ SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class); - SamplerThreadLocal.setSignalHandlerLocallyDisabled(true); - if (SamplerSampleWriterDataAccess.initialize(data, skipCount, maxDepth, true)) { - SamplerSampleWriter.begin(data); - /* Walk the stack. */ - Pointer sp = KnownIntrinsics.readCallerStackPointer(); - CodePointer ip = FrameAccess.singleton().readReturnAddress(sp); - if (JavaStackWalker.walkCurrentThread(sp, ip, SubstrateSigprofHandler.visitor()) || data.getTruncated()) { - acquireLock(); - try { - CIntPointer status = StackValue.get(CIntPointer.class); - Pointer start = data.getStartPos().add(SamplerSampleWriter.getHeaderSize()); - stackTraceId = getStackTraceId(start, data.getCurrentPos(), data.getHashCode(), status, false); - if (JfrStackTraceTableEntryStatus.get(status, JfrStackTraceTableEntryStatus.NEW)) { - SamplerSampleWriter.end(data); + SamplerThreadLocal.preventSigProfHandlerExecution(); + try { + if (SamplerSampleWriterDataAccess.initialize(data, skipCount, maxDepth, true)) { + SamplerSampleWriter.begin(data); + /* Walk the stack. */ + Pointer sp = KnownIntrinsics.readCallerStackPointer(); + CodePointer ip = FrameAccess.singleton().readReturnAddress(sp); + if (JavaStackWalker.walkCurrentThread(sp, ip, SubstrateSigprofHandler.visitor()) || data.getTruncated()) { + acquireLock(); + try { + CIntPointer status = StackValue.get(CIntPointer.class); + Pointer start = data.getStartPos().add(SamplerSampleWriter.getHeaderSize()); + stackTraceId = getStackTraceId(start, data.getCurrentPos(), data.getHashCode(), status, false); + if (JfrStackTraceTableEntryStatus.get(status, JfrStackTraceTableEntryStatus.NEW)) { + SamplerSampleWriter.end(data); + } + } finally { + releaseLock(); } - } finally { - releaseLock(); } } + } finally { + SamplerThreadLocal.allowSigProfHandlerExecution(); } - SamplerThreadLocal.setSignalHandlerLocallyDisabled(false); return stackTraceId; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java index cce40c004189..7d2d545d4257 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBufferAccess.java @@ -27,6 +27,7 @@ import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.word.Pointer; @@ -35,6 +36,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.util.UnsignedUtils; /** @@ -57,6 +59,8 @@ public static SamplerBuffer allocate(UnsignedWord dataSize) { if (result.isNonNull()) { result.setSize(dataSize); result.setFreeable(false); + result.setNext(WordFactory.nullPointer()); + result.setOwner(0); reinitialize(result); } return result; @@ -71,7 +75,6 @@ public static void free(SamplerBuffer buffer) { public static void reinitialize(SamplerBuffer buffer) { Pointer dataStart = getDataStart(buffer); buffer.setPos(dataStart); - buffer.setOwner(0L); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -88,4 +91,9 @@ public static boolean isEmpty(SamplerBuffer buffer) { public static Pointer getDataEnd(SamplerBuffer buffer) { return getDataStart(buffer).add(buffer.getSize()); } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean isOwner(SamplerBuffer buffer, IsolateThread thread) { + return buffer.getOwner() == SubstrateJVM.getThreadId(thread); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java index 5ae8be175b3d..3bd0c98bc9c4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerBuffersAccess.java @@ -59,41 +59,35 @@ private SamplerBuffersAccess() { public static void processActiveBuffers() { VMOperation.guaranteeInProgressAtSafepoint("Needed for iterating the threads"); - SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(true); - try { - for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { - SamplerBuffer buffer = SamplerThreadLocal.getThreadLocalBuffer(thread); - if (buffer.isNonNull()) { - processSamplerBuffer(buffer); - } + for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { + SamplerBuffer buffer = SamplerThreadLocal.getThreadLocalBuffer(thread); + if (buffer.isNonNull()) { + processSamplerBuffer(buffer); + + assert SamplerBufferAccess.isOwner(buffer, thread); + assert SamplerThreadLocal.getThreadLocalBuffer(thread) == buffer; } - } finally { - SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(false); } } @Uninterruptible(reason = "All code that accesses a sampler buffer must be uninterruptible.") public static void processFullBuffers() { - SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(true); - try { - while (true) { - /* Pop top buffer from stack of full buffers. */ - SamplerBuffer buffer = SubstrateSigprofHandler.singleton().fullBuffers().popBuffer(); - if (buffer.isNull()) { - /* No remaining buffers. */ - break; - } + while (true) { + /* Pop top buffer from stack of full buffers. */ + SamplerBuffer buffer = SubstrateSigprofHandler.singleton().fullBuffers().popBuffer(); + if (buffer.isNull()) { + /* No remaining buffers. */ + break; + } - /* Process the buffer. */ - processSamplerBuffer(buffer); - if (buffer.getFreeable()) { - SamplerBufferAccess.free(buffer); - } else { - SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer); - } + /* Process the buffer. */ + processSamplerBuffer(buffer); + if (buffer.getFreeable()) { + SamplerBufferAccess.free(buffer); + } else { + SamplerBufferAccess.reinitialize(buffer); + SubstrateSigprofHandler.singleton().availableBuffers().pushBuffer(buffer); } - } finally { - SubstrateSigprofHandler.singleton().setSignalHandlerGloballyDisabled(false); } } @@ -129,8 +123,11 @@ public static void processSamplerBuffer(SamplerBuffer buffer) { stackTraceRepo.acquireLock(); try { long stackTraceId = stackTraceRepo.getStackTraceId(current, current.add(sampleSize), sampleHash, status, true); + long owner = buffer.getOwner(); + assert owner != 0; + if (JfrStackTraceRepository.JfrStackTraceTableEntryStatus.get(status, JfrStackTraceRepository.JfrStackTraceTableEntryStatus.SERIALIZED)) { - ExecutionSampleEvent.writeExecutionSample(sampleTick, buffer.getOwner(), stackTraceId, threadState); + ExecutionSampleEvent.writeExecutionSample(sampleTick, owner, stackTraceId, threadState); /* Sample is already there, skip the rest of sample plus END_MARK symbol. */ current = current.add(sampleSize).add(SamplerSampleWriter.END_MARKER_SIZE); } else { @@ -140,7 +137,7 @@ public static void processSamplerBuffer(SamplerBuffer buffer) { while (current.belowThan(end)) { long ip = current.readLong(0); if (ip == SamplerSampleWriter.END_MARKER) { - ExecutionSampleEvent.writeExecutionSample(sampleTick, buffer.getOwner(), stackTraceId, threadState); + ExecutionSampleEvent.writeExecutionSample(sampleTick, owner, stackTraceId, threadState); current = current.add(SamplerSampleWriter.END_MARKER_SIZE); break; } else { @@ -153,6 +150,8 @@ public static void processSamplerBuffer(SamplerBuffer buffer) { stackTraceRepo.releaseLock(); } } + + SamplerBufferAccess.reinitialize(buffer); } @Uninterruptible(reason = "The handle should only be accessed from uninterruptible code to prevent that the GC frees the CodeInfo.", callerMustBe = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java index 1f29bc7e1b23..16cd489700b6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SamplerThreadLocal.java @@ -44,7 +44,7 @@ public class SamplerThreadLocal implements ThreadListener { private static final FastThreadLocalWord localBuffer = FastThreadLocalFactory.createWord("SamplerThreadLocal.localBuffer"); private static final FastThreadLocalLong missedSamples = FastThreadLocalFactory.createLong("SamplerThreadLocal.missedSamples"); private static final FastThreadLocalLong unparseableStacks = FastThreadLocalFactory.createLong("SamplerThreadLocal.unparseableStacks"); - private static final FastThreadLocalInt isSignalHandlerLocallyDisabled = FastThreadLocalFactory.createInt("SamplerThreadLocal.isSignalHandlerLocallyDisabled"); + private static final FastThreadLocalInt isSigProfHandlerDisabled = FastThreadLocalFactory.createInt("SamplerThreadLocal.isSignalHandlerDisabled"); /** * The data that we are using during the stack walk, allocated on the stack. */ @@ -139,13 +139,22 @@ public static long getUnparseableStacks() { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static void setSignalHandlerLocallyDisabled(boolean isDisabled) { - isSignalHandlerLocallyDisabled.set(isDisabled ? 1 : 0); + public static void preventSigProfHandlerExecution() { + int newValue = isSigProfHandlerDisabled.get() + 1; + assert newValue > 0; + isSigProfHandlerDisabled.set(newValue); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public static boolean isSignalHandlerLocallyDisabled() { - return isSignalHandlerLocallyDisabled.get() == 1; + public static void allowSigProfHandlerExecution() { + int newValue = isSigProfHandlerDisabled.get() - 1; + assert newValue >= 0; + isSigProfHandlerDisabled.set(newValue); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static boolean isSignalHandlerDisabled() { + return isSigProfHandlerDisabled.get() >= 1; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java index 6f87ad553788..17902b49ca01 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java @@ -56,6 +56,7 @@ import com.oracle.svm.core.graal.nodes.WriteHeapBaseNode; import com.oracle.svm.core.heap.VMOperationInfos; import com.oracle.svm.core.jdk.RuntimeSupport; +import com.oracle.svm.core.jdk.UninterruptibleUtils; import com.oracle.svm.core.jdk.management.ManagementFeature; import com.oracle.svm.core.jdk.management.SubstrateThreadMXBean; import com.oracle.svm.core.jfr.HasJfrSupport; @@ -72,7 +73,6 @@ import com.oracle.svm.core.util.VMError; @AutomaticallyRegisteredFeature -@SuppressWarnings("unused") class SubstrateSigprofHandlerFeature implements InternalFeature { @Override @@ -88,23 +88,16 @@ public List> getRequiredFeatures() { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { if (!SamplerHasSupport.get() && !HasJfrSupport.get()) { - /* No Sampler and JFR support. */ return; } - /* The common initialization part between Sampler and JFR. */ - - /* Create stack visitor. */ + /* Needed for both JFR and the sampler. */ ImageSingletons.add(SamplerStackWalkVisitor.class, new SamplerStackWalkVisitor()); - - /* Add thread listener. */ ThreadListenerSupport.get().register(new SamplerThreadLocal()); - /* Add startup hook. */ - RuntimeSupport.getRuntimeSupport().addStartupHook(new SubstrateSigprofHandlerStartupHook()); - /* The Sampler initialization part. */ if (SamplerHasSupport.get()) { + RuntimeSupport.getRuntimeSupport().addStartupHook(new SubstrateSigprofHandlerStartupHook()); VMError.guarantee(ImageSingletons.contains(RegisterDumper.class)); /* Add isolate listener. */ @@ -165,10 +158,9 @@ public void execute(boolean isFirstIsolate) { * @see SamplerBufferStack */ public abstract class SubstrateSigprofHandler { - public static class Options { - @Option(help = "Allow sampling-based profiling. Default: disabled in execution.")// - static final RuntimeOptionKey SamplingBasedProfiling = new RuntimeOptionKey<>(Boolean.FALSE) { + @Option(help = "Allow sampling-based profiling.")// + static final RuntimeOptionKey SamplingBasedProfiling = new RuntimeOptionKey<>(false) { @Override protected void onValueUpdate(EconomicMap, Object> values, Boolean oldValue, Boolean newValue) { if (newValue) { @@ -178,7 +170,7 @@ protected void onValueUpdate(EconomicMap, Object> values, Boolean o } }; - @SuppressWarnings("unused") @Option(help = "Start sampling-based profiling with options.")// + @Option(help = "Start sampling-based profiling with options.")// public static final RuntimeOptionKey StartSamplingBasedProfiling = new RuntimeOptionKey<>("") { @Override protected void onValueUpdate(EconomicMap, Object> values, String oldValue, String newValue) { @@ -191,15 +183,18 @@ protected void onValueUpdate(EconomicMap, Object> values, String ol } private boolean enabled; - private volatile boolean isSignalHandlerGloballyDisabled; private final SamplerBufferStack availableBuffers; private final SamplerBufferStack fullBuffers; + private final UninterruptibleUtils.AtomicInteger isSignalHandlerDisabledTemporarily; + private final UninterruptibleUtils.AtomicInteger threadsInSignalHandler; private SubstrateThreadMXBean threadMXBean; @Platforms(Platform.HOSTED_ONLY.class) protected SubstrateSigprofHandler() { this.availableBuffers = new SamplerBufferStack(); this.fullBuffers = new SamplerBufferStack(); + this.isSignalHandlerDisabledTemporarily = new UninterruptibleUtils.AtomicInteger(0); + this.threadsInSignalHandler = new UninterruptibleUtils.AtomicInteger(0); } @Fold @@ -207,6 +202,11 @@ public static SubstrateSigprofHandler singleton() { return ImageSingletons.lookup(SubstrateSigprofHandler.class); } + @Fold + static UninterruptibleUtils.AtomicInteger threadsInSignalHandler() { + return singleton().threadsInSignalHandler; + } + @Fold public static SamplerStackWalkVisitor visitor() { return ImageSingletons.lookup(SamplerStackWalkVisitor.class); @@ -222,13 +222,15 @@ boolean isProfilingEnabled() { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private boolean isSignalHandlerDisabled() { - return isSignalHandlerGloballyDisabled || SamplerThreadLocal.isSignalHandlerLocallyDisabled(); + public void preventThreadsFromEnteringSigProfHandler() { + int value = isSignalHandlerDisabledTemporarily.incrementAndGet(); + assert value > 0; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public void setSignalHandlerGloballyDisabled(boolean isDisabled) { - isSignalHandlerGloballyDisabled = isDisabled; + public void allowThreadsInSigProfHandler() { + int value = isSignalHandlerDisabledTemporarily.decrementAndGet(); + assert value >= 0; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -241,6 +243,13 @@ public SamplerBufferStack fullBuffers() { return fullBuffers; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void waitUntilAllThreadsExitedSignalHandler() { + while (threadsInSignalHandler.get() > 0) { + VMThreads.singleton().yield(); + } + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) SubstrateThreadMXBean substrateThreadMXBean() { return threadMXBean; @@ -250,20 +259,15 @@ SubstrateThreadMXBean substrateThreadMXBean() { * Installs the platform dependent sigprof handler. */ void install() { - if (SubstrateOptions.FlightRecorder.getValue()) { + if (SubstrateOptions.FlightRecorder.getValue() && Options.SamplingBasedProfiling.getValue() && isOSSupported()) { threadMXBean = (SubstrateThreadMXBean) ManagementFactory.getThreadMXBean(); + /* Call VM operation to initialize the sampler and the threads. */ InitializeSamplerOperation initializeSamplerOperation = new InitializeSamplerOperation(); initializeSamplerOperation.enqueue(); - if (Options.SamplingBasedProfiling.getValue()) { - if (isOSSupported()) { - /* After the VM operations finishes. Install handler and start profiling. */ - install0(); - } else { - VMError.shouldNotReachHere("Sampling-based profiling is currently supported only on LINUX!"); - } - } + /* After the VM operations finishes. Install handler and start profiling. */ + install0(); } } @@ -292,60 +296,67 @@ private static boolean isIPInJavaCode(RegisterDumper.Context uContext) { @Uninterruptible(reason = "The method executes during signal handling.", callerMustBe = true) protected static void doUninterruptibleStackWalk(RegisterDumper.Context uContext) { - CodePointer ip; - Pointer sp; - if (isIPInJavaCode(uContext)) { - ip = (CodePointer) RegisterDumper.singleton().getIP(uContext); - sp = (Pointer) RegisterDumper.singleton().getSP(uContext); - } else { - JavaFrameAnchor anchor = JavaFrameAnchors.getFrameAnchor(); - if (anchor.isNull()) { - /* - * The anchor is still null if the function is interrupted during prologue. See: - * com.oracle.svm.core.graal.snippets.CFunctionSnippets.prologueSnippet - */ + /* + * To prevent races, it is crucial that the thread count is incremented before doing any + * other checks. + */ + threadsInSignalHandler().incrementAndGet(); + try { + if (isProfilingDisallowed()) { + SamplerThreadLocal.increaseMissedSamples(); return; } - ip = anchor.getLastJavaIP(); - sp = anchor.getLastJavaSP(); - if (ip.isNull() || sp.isNull()) { + CodePointer ip; + Pointer sp; + if (isIPInJavaCode(uContext)) { + ip = (CodePointer) RegisterDumper.singleton().getIP(uContext); + sp = (Pointer) RegisterDumper.singleton().getSP(uContext); + } else { + JavaFrameAnchor anchor = JavaFrameAnchors.getFrameAnchor(); + if (anchor.isNull()) { + /* + * The anchor is still null if the function is interrupted during prologue. See: + * com.oracle.svm.core.graal.snippets.CFunctionSnippets.prologueSnippet + */ + return; + } + + ip = anchor.getLastJavaIP(); + sp = anchor.getLastJavaSP(); + if (ip.isNull() || sp.isNull()) { + /* + * It can happen that anchor is in list of all anchors, but its IP and SP are + * not filled yet. + */ + return; + } + } + + /* Initialize stack walk. */ + SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class); + /* Buffer size constrains stack walk size. */ + if (SamplerSampleWriterDataAccess.initialize(data, 0, Integer.MAX_VALUE, false)) { + SamplerSampleWriter.begin(data); /* - * It can happen that anchor is in list of all anchors, but its IP and SP are not - * filled yet. + * Walk the stack. + * + * We should commit the sample if: the stack walk was done successfully or the stack + * walk was interrupted because stack size exceeded given depth. */ - return; + if (JavaStackWalker.walkCurrentThread(sp, ip, visitor()) || data.getTruncated()) { + SamplerSampleWriter.end(data); + } } + } finally { + threadsInSignalHandler().decrementAndGet(); } + } - /* Test if the current thread's signal handler is disabled, or if holds the stack's lock. */ - if (singleton().isSignalHandlerDisabled() || singleton().availableBuffers().isLockedByCurrentThread() || singleton().fullBuffers().isLockedByCurrentThread()) { - /* - * The current thread already holds the stack's lock, so we can't access it. It's way - * better to lose one sample, then potentially the whole buffer. - * - * In case of disabled signal handler, if we proceed forward it could pollute the JFR - * output. - */ - SamplerThreadLocal.increaseMissedSamples(); - return; - } - - /* Initialize stack walk. */ - SamplerSampleWriterData data = StackValue.get(SamplerSampleWriterData.class); - /* Buffer size constrains stack walk size. */ - if (SamplerSampleWriterDataAccess.initialize(data, 0, Integer.MAX_VALUE, false)) { - SamplerSampleWriter.begin(data); - /* - * Walk the stack. - * - * We should commit the sample if: the stack walk was done successfully or the stack - * walk was interrupted because stack size exceeded given depth. - */ - if (JavaStackWalker.walkCurrentThread(sp, ip, visitor()) || data.getTruncated()) { - SamplerSampleWriter.end(data); - } - } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static boolean isProfilingDisallowed() { + return singleton().isSignalHandlerDisabledTemporarily.get() > 0 || SamplerThreadLocal.isSignalHandlerDisabled() || singleton().availableBuffers().isLockedByCurrentThread() || + singleton().fullBuffers().isLockedByCurrentThread(); } /** Called from the platform dependent sigprof handler to enter isolate. */ From 738bc199b8065a3c92bb6bc8f6d7f70f7092d453 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Mon, 21 Nov 2022 14:47:48 +0100 Subject: [PATCH 20/21] Minor fix. --- .../svm/core/posix/PosixSubstrateSigprofHandler.java | 11 ++++++++++- .../svm/core/sampler/SubstrateSigprofHandler.java | 6 +----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java index 103787588b26..1f7eda4eeb75 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java @@ -25,6 +25,8 @@ package com.oracle.svm.core.posix; +import java.util.function.BooleanSupplier; + import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -47,7 +49,7 @@ import com.oracle.svm.core.posix.headers.Time; import com.oracle.svm.core.sampler.SubstrateSigprofHandler; -@AutomaticallyRegisteredImageSingleton(SubstrateSigprofHandler.class) +@AutomaticallyRegisteredImageSingleton(value = SubstrateSigprofHandler.class, onlyWith = LinuxOnly.class) public class PosixSubstrateSigprofHandler extends SubstrateSigprofHandler { public static final long INTERVAL_S = 0; @@ -134,3 +136,10 @@ protected IsolateThread getThreadLocalKeyValue(UnsignedWord key) { return (IsolateThread) Pthread.pthread_getspecific((Pthread.pthread_key_t) key); } } + +class LinuxOnly implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return Platform.includedIn(Platform.LINUX.class); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java index fe53b2edcd68..83f16e636c9a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java @@ -213,10 +213,6 @@ public static SamplerStackWalkVisitor visitor() { return ImageSingletons.lookup(SamplerStackWalkVisitor.class); } - private static boolean isOSSupported() { - return Platform.includedIn(Platform.LINUX.class); - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) boolean isProfilingEnabled() { return enabled; @@ -260,7 +256,7 @@ SubstrateThreadMXBean substrateThreadMXBean() { * Installs the platform dependent sigprof handler. */ void install() { - if (JfrManager.isJFREnabled() && Options.SamplingBasedProfiling.getValue() && isOSSupported()) { + if (JfrManager.isJFREnabled() && Options.SamplingBasedProfiling.getValue()) { threadMXBean = (SubstrateThreadMXBean) ManagementFactory.getThreadMXBean(); /* Call VM operation to initialize the sampler and the threads. */ From ede2bfcc44b48814c4b2b3ecf741f095fbf43c5c Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Tue, 29 Nov 2022 15:06:25 +0100 Subject: [PATCH 21/21] Minor fixes. --- .../oracle/svm/core/jfr/JfrChunkWriter.java | 6 +-- .../core/sampler/SubstrateSigprofHandler.java | 43 ++++++++++++++----- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java index fd137a3dd331..1b063b855da2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrChunkWriter.java @@ -422,13 +422,13 @@ private void changeEpoch() { */ @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void processSamplerBuffers() { - SubstrateSigprofHandler.singleton().preventThreadsFromEnteringSigProfHandler(); + SubstrateSigprofHandler.preventThreadsFromEnteringSigProfHandler(); try { - SubstrateSigprofHandler.singleton().waitUntilAllThreadsExitedSignalHandler(); + SubstrateSigprofHandler.waitUntilAllThreadsExitedSignalHandler(); SamplerBuffersAccess.processActiveBuffers(); SamplerBuffersAccess.processFullBuffers(); } finally { - SubstrateSigprofHandler.singleton().allowThreadsInSigProfHandler(); + SubstrateSigprofHandler.allowThreadsInSigProfHandler(); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java index 83f16e636c9a..36762a4fc853 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/sampler/SubstrateSigprofHandler.java @@ -214,22 +214,50 @@ public static SamplerStackWalkVisitor visitor() { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - boolean isProfilingEnabled() { - return enabled; + public static void preventThreadsFromEnteringSigProfHandler() { + if (ImageSingletons.contains(SubstrateSigprofHandler.class)) { + singleton().preventThreadsFromEnteringSigProfHandler0(); + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public static void allowThreadsInSigProfHandler() { + if (ImageSingletons.contains(SubstrateSigprofHandler.class)) { + singleton().allowThreadsInSigProfHandler0(); + } } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public void preventThreadsFromEnteringSigProfHandler() { + public static void waitUntilAllThreadsExitedSignalHandler() { + if (ImageSingletons.contains(SubstrateSigprofHandler.class)) { + singleton().waitUntilAllThreadsExitedSignalHandler0(); + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private void preventThreadsFromEnteringSigProfHandler0() { int value = isSignalHandlerDisabledTemporarily.incrementAndGet(); assert value > 0; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public void allowThreadsInSigProfHandler() { + private void allowThreadsInSigProfHandler0() { int value = isSignalHandlerDisabledTemporarily.decrementAndGet(); assert value >= 0; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private void waitUntilAllThreadsExitedSignalHandler0() { + while (threadsInSignalHandler.get() > 0) { + VMThreads.singleton().yield(); + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + boolean isProfilingEnabled() { + return enabled; + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public SamplerBufferStack availableBuffers() { return availableBuffers; @@ -240,13 +268,6 @@ public SamplerBufferStack fullBuffers() { return fullBuffers; } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public void waitUntilAllThreadsExitedSignalHandler() { - while (threadsInSignalHandler.get() > 0) { - VMThreads.singleton().yield(); - } - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) SubstrateThreadMXBean substrateThreadMXBean() { return threadMXBean;