Skip to content

Commit 85fc2c4

Browse files
committed
Make JFR virtual thread registration lazy again
Localize thread-start metadata handling to JFR, centralize optional current-thread name lookup, and keep the web-image override aligned with PlatformThreads.
1 parent 42ffd7b commit 85fc2c4

32 files changed

Lines changed: 1143 additions & 104 deletions

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import com.oracle.svm.core.SubstrateOptions;
5151
import com.oracle.svm.core.UnmanagedMemoryUtil;
5252
import com.oracle.svm.core.c.NonmovableArray;
53+
import com.oracle.svm.core.c.struct.PinnedObjectField;
5354
import com.oracle.svm.core.code.CodeInfo;
5455
import com.oracle.svm.core.code.CodeInfoAccess;
5556
import com.oracle.svm.core.code.CodeInfoTable;
@@ -1284,10 +1285,30 @@ protected boolean hasWork(NativeVMOperationData data) {
12841285
/* Skip if any other GC happened in the meanwhile. */
12851286
return GCImpl.getGCImpl().getCollectionEpoch().equal(d.getRequestingEpoch());
12861287
}
1288+
1289+
@Override
1290+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
1291+
protected String getQueuingVThreadName(NativeVMOperationData data) {
1292+
return ((CollectionVMOperationData) data).getQueuingVThreadName();
1293+
}
1294+
1295+
@Override
1296+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
1297+
protected void setQueuingVThreadName(NativeVMOperationData data, String value) {
1298+
((CollectionVMOperationData) data).setQueuingVThreadName(value);
1299+
}
12871300
}
12881301

12891302
@RawStructure
12901303
private interface CollectionVMOperationData extends NativeVMOperationData {
1304+
@PinnedObjectField
1305+
@RawField
1306+
String getQueuingVThreadName();
1307+
1308+
@PinnedObjectField
1309+
@RawField
1310+
void setQueuingVThreadName(String value);
1311+
12911312
@RawField
12921313
int getCauseId();
12931314

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEventWriterAccess.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ public static Target_jdk_jfr_internal_event_EventWriter newEventWriter(JfrBuffer
5353

5454
long committedPos = buffer.getCommittedPos().rawValue();
5555
long maxPos = JfrBufferAccess.getDataEnd(buffer).rawValue();
56+
Thread currentThread = Thread.currentThread();
57+
if (JavaThreads.isVirtual(currentThread)) {
58+
SubstrateJVM.getThreadRepo().registerThread(currentThread);
59+
}
5660
long jfrThreadId = SubstrateJVM.getCurrentThreadId();
5761
boolean pinVirtualThread = JavaThreads.isCurrentThreadVirtual();
5862
return new Target_jdk_jfr_internal_event_EventWriter(committedPos, maxPos, jfrThreadId, true, pinVirtualThread, isCurrentThreadExcluded);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.oracle.svm.core.UnmanagedMemoryUtil;
3232
import com.oracle.svm.core.jdk.UninterruptibleUtils;
3333
import com.oracle.svm.core.jdk.UninterruptibleUtils.CharReplacer;
34+
import com.oracle.svm.core.thread.JavaThreads;
3435
import com.oracle.svm.core.util.DuplicatedInNativeCode;
3536
import com.oracle.svm.shared.Uninterruptible;
3637
import com.oracle.svm.shared.util.VMError;
@@ -242,23 +243,48 @@ public static void putString(JfrNativeEventWriterData data, Pointer utf8Buffer,
242243

243244
@Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
244245
public static void putEventThread(JfrNativeEventWriterData data) {
245-
putThread(data, SubstrateJVM.getCurrentThreadId());
246+
putCurrentThread(data);
247+
}
248+
249+
@Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
250+
public static void putCurrentThread(JfrNativeEventWriterData data) {
251+
Thread thread = JavaThreads.getCurrentThreadOrNull();
252+
if (thread != null && JavaThreads.isVirtual(thread)) {
253+
SubstrateJVM.getThreadRepo().registerThread(thread);
254+
}
255+
putRegisteredThreadId(data, SubstrateJVM.getCurrentThreadId());
246256
}
247257

248258
@Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
249259
public static void putThread(JfrNativeEventWriterData data, Thread thread) {
250260
if (thread == null) {
251-
putThread(data, 0L);
261+
putRegisteredThreadId(data, 0L);
252262
} else {
253-
putThread(data, SubstrateJVM.getThreadId(thread));
263+
if (JavaThreads.isVirtual(thread)) {
264+
SubstrateJVM.getThreadRepo().registerThread(thread);
265+
}
266+
putRegisteredThreadId(data, SubstrateJVM.getThreadId(thread));
254267
}
255268
}
256269

257270
@Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
258-
public static void putThread(JfrNativeEventWriterData data, long threadId) {
271+
public static void putRegisteredThreadId(JfrNativeEventWriterData data, long threadId) {
259272
putLong(data, threadId);
260273
}
261274

275+
@Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
276+
public static void putRegisteredThreadId(JfrNativeEventWriterData data, long threadId, String virtualThreadName) {
277+
if (virtualThreadName != null) {
278+
/*
279+
* Delayed event paths may retain only a virtual thread id across chunk rotations. In
280+
* that case, re-register the virtual thread metadata in the current epoch before
281+
* writing the reference.
282+
*/
283+
SubstrateJVM.getThreadRepo().registerVirtualThread(threadId, virtualThreadName);
284+
}
285+
putRegisteredThreadId(data, threadId);
286+
}
287+
262288
@Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
263289
public static void putClass(JfrNativeEventWriterData data, Class<?> aClass) {
264290
if (aClass == null) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadLocal.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,16 @@ public void teardown() {
132132
@Uninterruptible(reason = "Only uninterruptible code may be executed before the thread is fully started.")
133133
@Override
134134
public void beforeThreadStart(IsolateThread isolateThread, Thread javaThread) {
135+
Target_java_lang_Thread targetThread = SubstrateUtil.cast(javaThread, Target_java_lang_Thread.class);
136+
long parentThreadId = targetThread.jfrParentThreadId;
137+
String parentVThreadName = targetThread.jfrParentVThreadName;
138+
targetThread.jfrParentThreadId = 0L;
139+
targetThread.jfrParentVThreadName = null;
140+
135141
if (SubstrateJVM.get().isRecording()) {
136142
SubstrateJVM.getThreadRepo().registerThread(javaThread);
137143
ThreadCPULoadEvent.initWallclockTime(isolateThread);
138-
ThreadStartEvent.emit(javaThread);
144+
ThreadStartEvent.emit(javaThread, parentThreadId, parentVThreadName);
139145
}
140146
}
141147

@@ -267,15 +273,26 @@ public static boolean isThreadExcluded(Thread thread) {
267273

268274
public static Target_jdk_jfr_internal_event_EventWriter getEventWriter() {
269275
Target_jdk_jfr_internal_event_EventWriter eventWriter = javaEventWriter.get();
270-
/*
271-
* EventWriter objects cache various thread-specific values. Virtual threads use the
272-
* EventWriter object of their carrier thread, so we need to update all cached values so
273-
* that they match the virtual thread.
274-
*/
275-
if (eventWriter != null && eventWriter.threadID != SubstrateJVM.getCurrentThreadId()) {
276-
eventWriter.threadID = SubstrateJVM.getCurrentThreadId();
277-
Target_java_lang_Thread tjlt = SubstrateUtil.cast(Thread.currentThread(), Target_java_lang_Thread.class);
278-
eventWriter.excluded = tjlt.jfrExcluded;
276+
if (eventWriter != null) {
277+
Thread currentThread = Thread.currentThread();
278+
if (JavaThreads.isVirtual(currentThread)) {
279+
/*
280+
* The Java-level EventWriter can stay associated with a long-lived virtual thread
281+
* across chunk rotations. Re-register the vthread so its constant pool entry is
282+
* present in the current epoch before the next event is committed.
283+
*/
284+
SubstrateJVM.getThreadRepo().registerThread(currentThread);
285+
}
286+
/*
287+
* EventWriter objects cache various thread-specific values. Virtual threads use the
288+
* EventWriter object of their carrier thread, so we need to update all cached values so
289+
* that they match the virtual thread.
290+
*/
291+
if (eventWriter.threadID != SubstrateJVM.getCurrentThreadId()) {
292+
eventWriter.threadID = SubstrateJVM.getCurrentThreadId();
293+
Target_java_lang_Thread tjlt = SubstrateUtil.cast(currentThread, Target_java_lang_Thread.class);
294+
eventWriter.excluded = tjlt.jfrExcluded;
295+
}
279296
}
280297
return eventWriter;
281298
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,27 @@ public void registerThread(Thread thread) {
126126
registerThread0(thread, isVirtual);
127127
}
128128

129+
@Uninterruptible(reason = "Prevent epoch changes. Prevent races with VM operations that start/stop recording.")
130+
public void registerVirtualThread(long threadId, String name) {
131+
if (!SubstrateJVM.get().isRecording() || threadId == 0L || name == null) {
132+
return;
133+
}
134+
135+
registerThread0(threadId, name, 0L, true, null, null);
136+
}
137+
129138
@Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.")
130139
private void registerThread0(Thread thread, boolean isVirtual) {
131140
long threadId = JavaThreads.getThreadId(thread);
141+
long osThreadId = isVirtual ? 0 : threadId;
142+
String name = thread.getName();
143+
ThreadGroup threadGroup = isVirtual ? null : JavaThreads.getRawThreadGroup(thread);
144+
Target_java_lang_VirtualThread vthread = isVirtual ? JavaThreads.toVirtualTarget(thread) : null;
145+
registerThread0(threadId, name, osThreadId, isVirtual, threadGroup, vthread);
146+
}
147+
148+
@Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.")
149+
private void registerThread0(long threadId, String name, long osThreadId, boolean isVirtual, ThreadGroup threadGroup, Target_java_lang_VirtualThread vthread) {
132150
JfrVisited visitedThread = StackValue.get(JfrVisited.class);
133151
visitedThread.setId(threadId);
134152
visitedThread.setHash(UninterruptibleUtils.Long.hashCode(threadId));
@@ -137,6 +155,9 @@ private void registerThread0(Thread thread, boolean isVirtual) {
137155
try {
138156
JfrThreadEpochData epochData = getEpochData(false);
139157
if (!epochData.threadTable.putIfAbsent(visitedThread)) {
158+
if (vthread != null) {
159+
vthread.jfrEpochId = JfrTraceIdEpoch.getInstance().currentEpochId();
160+
}
140161
return;
141162
}
142163

@@ -149,9 +170,7 @@ private void registerThread0(Thread thread, boolean isVirtual) {
149170
JfrNativeEventWriterDataAccess.initialize(data, epochData.threadBuffer);
150171

151172
/* Similar to JfrThreadConstant::serialize in HotSpot. */
152-
long osThreadId = isVirtual ? 0 : threadId;
153-
long threadGroupId = registerThreadGroup(thread, isVirtual);
154-
String name = thread.getName();
173+
long threadGroupId = isVirtual ? registerThreadGroup0(Target_java_lang_Thread.virtualThreadGroup()) : registerThreadGroup0(threadGroup);
155174

156175
JfrNativeEventWriter.putLong(data, threadId);
157176
JfrNativeEventWriter.putString(data, name); // OS thread name
@@ -164,8 +183,7 @@ private void registerThread0(Thread thread, boolean isVirtual) {
164183
return;
165184
}
166185

167-
if (isVirtual) {
168-
Target_java_lang_VirtualThread vthread = JavaThreads.toVirtualTarget(thread);
186+
if (vthread != null) {
169187
vthread.jfrEpochId = JfrTraceIdEpoch.getInstance().currentEpochId();
170188
}
171189

@@ -193,16 +211,6 @@ private static boolean isVirtualThreadAlreadyRegistered(Thread thread) {
193211
return vthread.jfrEpochId == epochId;
194212
}
195213

196-
@Uninterruptible(reason = "Epoch must not change while in this method.")
197-
private long registerThreadGroup(Thread thread, boolean isVirtual) {
198-
if (isVirtual) {
199-
/* For virtual threads, a fixed thread group id is reserved. */
200-
return VIRTUAL_THREAD_GROUP_ID;
201-
}
202-
ThreadGroup group = JavaThreads.getRawThreadGroup(thread);
203-
return registerThreadGroup0(group);
204-
}
205-
206214
@Uninterruptible(reason = "Epoch must not change while in this method.")
207215
private long registerThreadGroup0(ThreadGroup threadGroup) {
208216
if (threadGroup == null) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@
2424
*/
2525
package com.oracle.svm.core.jfr;
2626

27+
import static com.oracle.svm.shared.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE;
28+
2729
import java.util.List;
2830

2931
import com.oracle.svm.core.os.RawFileOperationSupport;
3032
import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor;
33+
import org.graalvm.nativeimage.CurrentIsolate;
3134
import org.graalvm.nativeimage.ImageSingletons;
3235
import org.graalvm.nativeimage.IsolateThread;
3336
import org.graalvm.nativeimage.Platform;
@@ -233,6 +236,11 @@ protected boolean isRecording() {
233236
return recording;
234237
}
235238

239+
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
240+
public static boolean isRecordingActive() {
241+
return HasJfrSupport.get() && get().isRecording();
242+
}
243+
236244
/**
237245
* See {@link JVM#createJFR}. Until {@link #beginRecording} is executed, no JFR events can be
238246
* triggered yet. So, we don't need to take any precautions here.
@@ -331,6 +339,17 @@ public static long getCurrentThreadId() {
331339
return 0;
332340
}
333341

342+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
343+
public static String getOptionalCurrentThreadName() {
344+
if (isRecordingActive() && CurrentIsolate.getCurrentThread().isNonNull()) {
345+
Thread thread = JavaThreads.getCurrentThreadOrNull();
346+
if (thread != null && JavaThreads.isVirtual(thread)) {
347+
return thread.getName();
348+
}
349+
}
350+
return null;
351+
}
352+
334353
/**
335354
* See {@link JVM#storeMetadataDescriptor}.
336355
*/

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecuteVMOperationEvent.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,16 @@
3939
import com.oracle.svm.core.thread.VMOperation;
4040

4141
public class ExecuteVMOperationEvent {
42-
public static void emit(VMOperation vmOperation, long requestingThreadId, long startTicks) {
42+
public static void emit(VMOperation vmOperation, long requestingThreadId, String requestingVThreadName, long startTicks) {
4343
if (!HasJfrSupport.get()) {
4444
return;
4545
}
4646

47-
emit0(vmOperation, requestingThreadId, startTicks);
47+
emit0(vmOperation, requestingThreadId, requestingVThreadName, startTicks);
4848
}
4949

5050
@Uninterruptible(reason = "Accesses a JFR buffer.")
51-
private static void emit0(VMOperation vmOperation, long requestingThreadId, long startTicks) {
51+
private static void emit0(VMOperation vmOperation, long requestingThreadId, String requestingVThreadName, long startTicks) {
5252
long duration = JfrTicks.duration(startTicks);
5353
if (JfrEvent.ExecuteVMOperation.shouldEmit(duration)) {
5454
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
@@ -61,7 +61,7 @@ private static void emit0(VMOperation vmOperation, long requestingThreadId, long
6161
JfrNativeEventWriter.putLong(data, vmOperation.getId() + 1); // id starts with 1
6262
JfrNativeEventWriter.putBoolean(data, vmOperation.getCausesSafepoint());
6363
JfrNativeEventWriter.putBoolean(data, vmOperation.isBlocking());
64-
JfrNativeEventWriter.putThread(data, requestingThreadId);
64+
JfrNativeEventWriter.putRegisteredThreadId(data, requestingThreadId, requestingVThreadName);
6565
JfrNativeEventWriter.putLong(data, vmOperation.getCausesSafepoint() ? Safepoint.singleton().getSafepointId().rawValue() : 0);
6666
JfrNativeEventWriter.endSmallEvent(data);
6767
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public static void writeExecutionSample(long elapsedTicks, long threadId, long s
4141

4242
JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ExecutionSample);
4343
JfrNativeEventWriter.putLong(data, elapsedTicks);
44-
JfrNativeEventWriter.putThread(data, threadId);
44+
JfrNativeEventWriter.putRegisteredThreadId(data, threadId);
4545
JfrNativeEventWriter.putLong(data, stackTraceId);
4646
JfrNativeEventWriter.putLong(data, threadState);
4747
JfrNativeEventWriter.endSmallEvent(data);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@
4040
import org.graalvm.word.impl.Word;
4141

4242
public class JavaMonitorEnterEvent {
43-
public static void emit(Object obj, long previousOwnerTid, long startTicks) {
43+
public static void emit(Object obj, long previousOwnerTid, String previousOwnerVThreadName, long startTicks) {
4444
if (HasJfrSupport.get()) {
45-
emit0(obj, previousOwnerTid, startTicks);
45+
emit0(obj, previousOwnerTid, previousOwnerVThreadName, startTicks);
4646
}
4747
}
4848

4949
@Uninterruptible(reason = "Accesses a JFR buffer.")
50-
public static void emit0(Object obj, long previousOwnerTid, long startTicks) {
50+
public static void emit0(Object obj, long previousOwnerTid, String previousOwnerVThreadName, long startTicks) {
5151
long duration = JfrTicks.duration(startTicks);
5252
if (JfrEvent.JavaMonitorEnter.shouldEmit(duration)) {
5353
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
@@ -59,7 +59,7 @@ public static void emit0(Object obj, long previousOwnerTid, long startTicks) {
5959
JfrNativeEventWriter.putEventThread(data);
6060
JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorEnter));
6161
JfrNativeEventWriter.putClass(data, obj.getClass());
62-
JfrNativeEventWriter.putThread(data, previousOwnerTid);
62+
JfrNativeEventWriter.putRegisteredThreadId(data, previousOwnerTid, previousOwnerVThreadName);
6363
JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue());
6464
JfrNativeEventWriter.endSmallEvent(data);
6565
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@
3939
import org.graalvm.word.impl.Word;
4040

4141
public class JavaMonitorWaitEvent {
42-
public static void emit(long startTicks, Object obj, long notifierTid, long timeout, boolean timedOut) {
42+
public static void emit(long startTicks, Object obj, long notifierTid, String notifierVThreadName, long timeout, boolean timedOut) {
4343
if (HasJfrSupport.get() && obj != null && !Target_jdk_jfr_internal_management_HiddenWait.class.equals(obj.getClass())) {
44-
emit0(startTicks, obj, notifierTid, timeout, timedOut);
44+
emit0(startTicks, obj, notifierTid, notifierVThreadName, timeout, timedOut);
4545
}
4646
}
4747

4848
@Uninterruptible(reason = "Accesses a JFR buffer.")
49-
private static void emit0(long startTicks, Object obj, long notifierTid, long timeout, boolean timedOut) {
49+
private static void emit0(long startTicks, Object obj, long notifierTid, String notifierVThreadName, long timeout, boolean timedOut) {
5050
long duration = JfrTicks.duration(startTicks);
5151
if (JfrEvent.JavaMonitorWait.shouldEmit(duration)) {
5252
JfrNativeEventWriterData data = org.graalvm.nativeimage.StackValue.get(JfrNativeEventWriterData.class);
@@ -58,7 +58,7 @@ private static void emit0(long startTicks, Object obj, long notifierTid, long ti
5858
JfrNativeEventWriter.putEventThread(data);
5959
JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorWait));
6060
JfrNativeEventWriter.putClass(data, obj.getClass());
61-
JfrNativeEventWriter.putThread(data, notifierTid);
61+
JfrNativeEventWriter.putRegisteredThreadId(data, notifierTid, notifierVThreadName);
6262
JfrNativeEventWriter.putLong(data, timeout);
6363
JfrNativeEventWriter.putBoolean(data, timedOut);
6464
JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue());

0 commit comments

Comments
 (0)