diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java index 4d23fe0f96e0..0e77cd0f0e18 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.jfr; -import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; @@ -35,6 +34,8 @@ import com.oracle.svm.core.util.DuplicatedInNativeCode; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.word.Word; + /** * A JFR event writer that does not allocate any objects in the Java heap. Can only be used from * {@link Uninterruptible} code to prevent races between threads that try to write a native JFR diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index 62bd2f978384..59bc7d635fe8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.jfr; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -40,9 +39,12 @@ import com.oracle.svm.core.thread.JavaThreads; import com.oracle.svm.core.thread.PlatformThreads; import com.oracle.svm.core.thread.Target_java_lang_Thread; +import com.oracle.svm.core.thread.Target_java_lang_VirtualThread; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMThreads; +import jdk.graal.compiler.word.Word; + /** * Repository that collects all metadata about threads and thread groups. * @@ -101,14 +103,23 @@ public void registerRunningThreads() { } } - @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") + @Uninterruptible(reason = "Prevent epoch changes. Prevent races with VM operations that start/stop recording.") public void registerThread(Thread thread) { if (!SubstrateJVM.get().isRecording()) { return; } - long threadId = JavaThreads.getThreadId(thread); + boolean isVirtual = JavaThreads.isVirtual(thread); + if (isVirtual && isVirtualThreadAlreadyRegistered(thread)) { + return; + } + + registerThread0(thread, isVirtual); + } + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") + private void registerThread0(Thread thread, boolean isVirtual) { + long threadId = JavaThreads.getThreadId(thread); JfrVisited visitedThread = StackValue.get(JfrVisited.class); visitedThread.setId(threadId); visitedThread.setHash(UninterruptibleUtils.Long.hashCode(threadId)); @@ -129,14 +140,14 @@ public void registerThread(Thread thread) { JfrNativeEventWriterDataAccess.initialize(data, epochData.threadBuffer); /* Similar to JfrThreadConstant::serialize in HotSpot. */ - boolean isVirtual = JavaThreads.isVirtual(thread); long osThreadId = isVirtual ? 0 : threadId; long threadGroupId = registerThreadGroup(thread, isVirtual); + String name = thread.getName(); JfrNativeEventWriter.putLong(data, threadId); - JfrNativeEventWriter.putString(data, thread.getName()); // OS thread name + JfrNativeEventWriter.putString(data, name); // OS thread name JfrNativeEventWriter.putLong(data, osThreadId); // OS thread id - JfrNativeEventWriter.putString(data, thread.getName()); // Java thread name + JfrNativeEventWriter.putString(data, name); // Java thread name JfrNativeEventWriter.putLong(data, threadId); // Java thread id JfrNativeEventWriter.putLong(data, threadGroupId); // Java thread group JfrNativeEventWriter.putBoolean(data, isVirtual); @@ -144,6 +155,11 @@ public void registerThread(Thread thread) { return; } + if (isVirtual) { + Target_java_lang_VirtualThread vthread = JavaThreads.toVirtualTarget(thread); + vthread.jfrEpochId = JfrTraceIdEpoch.getInstance().currentEpochId(); + } + epochData.unflushedThreadCount++; /* The buffer may have been replaced with a new one. */ epochData.threadBuffer = data.getJfrBuffer(); @@ -152,6 +168,16 @@ public void registerThread(Thread thread) { } } + @Uninterruptible(reason = "Epoch must not change while in this method.", callerMustBe = true) + private static boolean isVirtualThreadAlreadyRegistered(Thread thread) { + assert JavaThreads.isVirtual(thread); + + /* Threads only need to be registered once per epoch. */ + Target_java_lang_VirtualThread vthread = JavaThreads.toVirtualTarget(thread); + long epochId = JfrTraceIdEpoch.getInstance().currentEpochId(); + return vthread.jfrEpochId == epochId; + } + @Uninterruptible(reason = "Epoch must not change while in this method.") private long registerThreadGroup(Thread thread, boolean isVirtual) { if (isVirtual) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java index 1a6723b9a532..0a14b381b7af 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java @@ -41,7 +41,7 @@ public static void writeExecutionSample(long elapsedTicks, long threadId, long s JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ExecutionSample); JfrNativeEventWriter.putLong(data, elapsedTicks); - JfrNativeEventWriter.putLong(data, threadId); + JfrNativeEventWriter.putThread(data, threadId); JfrNativeEventWriter.putLong(data, stackTraceId); JfrNativeEventWriter.putLong(data, threadState); JfrNativeEventWriter.endSmallEvent(data); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java index 671924ba1a48..1d72ef3af785 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java @@ -26,7 +26,6 @@ package com.oracle.svm.core.jfr.events; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import com.oracle.svm.core.Uninterruptible; @@ -38,6 +37,8 @@ import com.oracle.svm.core.jfr.JfrTicks; import com.oracle.svm.core.jfr.SubstrateJVM; +import jdk.graal.compiler.word.Word; + public class JavaMonitorEnterEvent { public static void emit(Object obj, long previousOwnerTid, long startTicks) { if (HasJfrSupport.get()) { @@ -58,7 +59,7 @@ public static void emit0(Object obj, long previousOwnerTid, long startTicks) { JfrNativeEventWriter.putEventThread(data); JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorEnter, 0)); JfrNativeEventWriter.putClass(data, obj.getClass()); - JfrNativeEventWriter.putLong(data, previousOwnerTid); + JfrNativeEventWriter.putThread(data, previousOwnerTid); JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue()); JfrNativeEventWriter.endSmallEvent(data); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java index 27322780e18a..3c474ab4ba70 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java @@ -40,15 +40,15 @@ import jdk.graal.compiler.word.Word; public class JavaMonitorWaitEvent { - public static void emit(long startTicks, Object obj, long notifier, long timeout, boolean timedOut) { + public static void emit(long startTicks, Object obj, long notifierTid, long timeout, boolean timedOut) { if (HasJfrSupport.get() && obj != null && !Target_jdk_jfr_internal_JVM_ChunkRotationMonitor.class.equals(obj.getClass()) && !Target_jdk_jfr_internal_management_HiddenWait.class.equals(obj.getClass())) { - emit0(startTicks, obj, notifier, timeout, timedOut); + emit0(startTicks, obj, notifierTid, timeout, timedOut); } } @Uninterruptible(reason = "Accesses a JFR buffer.") - private static void emit0(long startTicks, Object obj, long notifier, long timeout, boolean timedOut) { + private static void emit0(long startTicks, Object obj, long notifierTid, long timeout, boolean timedOut) { long duration = JfrTicks.duration(startTicks); if (JfrEvent.JavaMonitorWait.shouldEmit(duration)) { JfrNativeEventWriterData data = org.graalvm.nativeimage.StackValue.get(JfrNativeEventWriterData.class); @@ -60,7 +60,7 @@ private static void emit0(long startTicks, Object obj, long notifier, long timeo JfrNativeEventWriter.putEventThread(data); JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorWait, 0)); JfrNativeEventWriter.putClass(data, obj.getClass()); - JfrNativeEventWriter.putLong(data, notifier); + JfrNativeEventWriter.putThread(data, notifierTid); JfrNativeEventWriter.putLong(data, timeout); JfrNativeEventWriter.putBoolean(data, timedOut); JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java index 1eb36418442e..e6965cba15e1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java @@ -47,7 +47,7 @@ public static void emit(Thread thread) { JfrNativeEventWriter.putEventThread(data); JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ThreadStart, 0)); JfrNativeEventWriter.putThread(data, thread); - JfrNativeEventWriter.putLong(data, JavaThreads.getParentThreadId(thread)); + JfrNativeEventWriter.putThread(data, JavaThreads.getParentThreadId(thread)); JfrNativeEventWriter.endSmallEvent(data); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java index 400d919c4973..b0ce23c7f153 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java @@ -25,7 +25,6 @@ package com.oracle.svm.core.jfr.traceid; -import jdk.graal.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -33,6 +32,8 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.thread.VMOperation; +import jdk.graal.compiler.api.replacements.Fold; + /** * Class holding the current JFR epoch. JFR uses an epoch system to safely separate constant pool * entries between adjacent chunks. Used to get the current or previous epoch and switch from one @@ -42,7 +43,7 @@ public class JfrTraceIdEpoch { private static final long EPOCH_0_BIT = 0b01; private static final long EPOCH_1_BIT = 0b10; - private boolean epoch; + private long epochId; @Fold public static JfrTraceIdEpoch getInstance() { @@ -56,26 +57,36 @@ public JfrTraceIdEpoch() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void changeEpoch() { assert VMOperation.isInProgressAtSafepoint(); - epoch = !epoch; + epochId++; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) long thisEpochBit() { - return epoch ? EPOCH_1_BIT : EPOCH_0_BIT; + return getEpoch() ? EPOCH_1_BIT : EPOCH_0_BIT; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) long previousEpochBit() { - return epoch ? EPOCH_0_BIT : EPOCH_1_BIT; + return getEpoch() ? EPOCH_0_BIT : EPOCH_1_BIT; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean currentEpoch() { - return epoch; + return getEpoch(); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean previousEpoch() { - return !epoch; + return !getEpoch(); + } + + @Uninterruptible(reason = "Prevent epoch from changing.", callerMustBe = true) + public long currentEpochId() { + return epochId; + } + + @Uninterruptible(reason = "Prevent epoch from changing.", callerMustBe = true) + private boolean getEpoch() { + return (epochId & 1) == 0; } } 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 4f8d60284a79..d1d811858b07 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 @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.thread; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + import java.lang.Thread.UncaughtExceptionHandler; import java.security.AccessControlContext; import java.security.AccessController; @@ -220,8 +222,9 @@ static Target_java_lang_ThreadGroup toTarget(ThreadGroup threadGroup) { return Target_java_lang_ThreadGroup.class.cast(threadGroup); } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) @SuppressFBWarnings(value = "BC", justification = "Cast for @TargetClass") - private static Target_java_lang_VirtualThread toVirtualTarget(Thread thread) { + public static Target_java_lang_VirtualThread toVirtualTarget(Thread thread) { return Target_java_lang_VirtualThread.class.cast(thread); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_VirtualThread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_VirtualThread.java index 616c4d274c0b..38beaccb700c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_VirtualThread.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_VirtualThread.java @@ -122,6 +122,10 @@ public final class Target_java_lang_VirtualThread { @TargetElement(onlyWith = JDKLatest.class) @Alias @InjectAccessors(AlwaysFalseAccessor.class) boolean notified; // Checkstyle: resume + @Inject // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ResetToMinusOneTransformer.class) // + public long jfrEpochId = -1; + @Alias private static native ForkJoinPool createDefaultScheduler(); @@ -598,3 +602,10 @@ static Thread asThread(Object obj) { private VirtualThreadHelper() { } } + +final class ResetToMinusOneTransformer implements FieldValueTransformer { + @Override + public Object transform(Object receiver, Object originalValue) { + return -1L; + } +}