-
Bug
-
Resolution: Fixed
-
P4
-
21
-
b15
-
generic
-
generic
Issue | Fix Version | Assignee | Priority | Status | Resolution | Resolved In Build |
---|---|---|---|---|---|---|
JDK-8323386 | 21.0.3-oracle | Man Cao | P4 | Resolved | Fixed | b01 |
JDK-8320109 | 21.0.2 | Man Cao | P4 | Resolved | Fixed | b08 |
Ubunu 22.10 x86_64
openjdk version "22-ea" 2024-03-19
OpenJDK Runtime Environment (build 22-ea+6-393)
OpenJDK 64-Bit Server VM (build 22-ea+6-393, mixed mode, sharing)
openjdk version "21-ea" 2023-09-19
OpenJDK Runtime Environment (build 21-ea+31-2444)
OpenJDK 64-Bit Server VM (build 21-ea+31-2444, mixed mode, sharing)
commit 81c4e8f916a04582698907291b6505d4484cf9c2
from https://github.com/openjdk/jdk.git
A DESCRIPTION OF THE PROBLEM :
VirtualThreadEnd events are not posted for virtual threads that were parked while an agent was loaded into a running JVM. This also applied to the mount/unmount extension events.
These events are posted for virtual threads that were mounted during attach. In the builds mentioned above, events for mounted vhtreads were incomplete, but with the fix for
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
With the attached sample and JAVA_HOME set to a JDK 21 or 22:
g++ -std=c++11 -shared -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC VThreadEventTest.cpp -o libVThreadEventTest.so
$JAVA_HOME/bin/javac VThreadEventTest.java
LD_LIBRARY_PATH=. $JAVA_HOME/bin/java -Djdk.attach.allowAttachSelf=true -XX:+EnableDynamicAgentLoading VThreadEventTest
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Process exits with 0.
ACTUAL -
Process exits with 1 and prints
end: 4 (exp: 17), unmount: 7 (exp: 20), mount: 0 (exp: 13)
unexpected count
for the builds mentioned above and
end: 7 (exp: 17), unmount: 10 (exp: 20), mount: 3 (exp: 13)
unexpected count
with a build from the repo at the commit mentioned above.
---------- BEGIN SOURCE ----------
-- VThreadEventTest.java -----------------------------------------------------------
import com.sun.tools.attach.VirtualMachine;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.LockSupport;
public class VThreadEventTest {
private static native int getEndCount();
private static native int getMountCount();
private static native int getUnmountCount();
private static volatile boolean attached;
public static void main(String[] args) throws Exception {
if (Runtime.getRuntime().availableProcessors() < 8) {
System.out.println("WARNING: test expects at least 8 processors.");
}
try (ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()) {
for (int threadCount = 0; threadCount < 10; threadCount++) {
executorService.execute(() -> {
LockSupport.parkNanos(1_000_000L * 7_000);
});
}
for (int threadCount = 0; threadCount < 4; threadCount++) {
executorService.execute(() -> {
while (!attached) {
// keep mounted
}
});
}
for (int threadCount = 0; threadCount < 3; threadCount++) {
executorService.execute(() -> {
while (!attached) {
// keep mounted
}
LockSupport.parkNanos(1_000_000L * 100);
});
}
Thread.sleep(2_000);
VirtualMachine vm = VirtualMachine.attach(String.valueOf(ProcessHandle.current().pid()));
vm.loadAgentLibrary("VThreadEventTest");
Thread.sleep(500);
attached = true;
}
int endCount = getEndCount();
int unmountCount = getUnmountCount();
int mountCount = getMountCount();
int endExpected = 10 + 4 + 3;
int unmountExpected = 10 + 4 + 3 * 2;
int mountExpected = 10 + 3;
System.out.println("end: " + endCount + " (exp: " + endExpected + "), unmount: " + unmountCount +
" (exp: " + unmountExpected + "), mount: " + mountCount + " (exp: " + mountExpected + ")");
if (endCount != endExpected || unmountCount != unmountExpected || mountCount != mountExpected) {
System.out.println("unexpected count");
System.exit(1);
}
}
}
------------------------------------------------------------------------------------
-- VThreadEventTest.java -----------------------------------------------------------
#include <jvmti.h>
#include <cstring>
#include <mutex>
#ifdef _WIN32
#define VARIADICJNI __cdecl
#else
#define VARIADICJNI JNICALL
#endif
namespace {
jvmtiEnv *jvmti = nullptr;
std::mutex lock;
int endCount = 0;
int unmountCount = 0;
int mountCount = 0;
void checkJvmti(int code, const char* message) {
if (code != JVMTI_ERROR_NONE) {
printf("Error %s: %d\n", message, code);
abort();
}
}
void JNICALL vthreadEnd(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread virtual_thread) {
std::lock_guard<std::mutex> lockGuard(lock);
endCount++;
}
void VARIADICJNI vthreadUnmount(jvmtiEnv* jvmti_env, ...) {
std::lock_guard<std::mutex> lockGuard(lock);
unmountCount++;
}
void VARIADICJNI vthreadMount(jvmtiEnv* jvmti_env, ...) {
std::lock_guard<std::mutex> lockGuard(lock);
mountCount++;
}
}
extern "C" JNIEXPORT jint JNICALL Java_VThreadEventTest_getEndCount(JNIEnv* jni_env, jclass clazz) {
std::lock_guard<std::mutex> lockGuard(lock);
return endCount;
}
extern "C" JNIEXPORT jint JNICALL Java_VThreadEventTest_getMountCount(JNIEnv* jni_env, jclass clazz) {
std::lock_guard<std::mutex> lockGuard(lock);
return mountCount;
}
extern "C" JNIEXPORT jint JNICALL Java_VThreadEventTest_getUnmountCount(JNIEnv* jni_env, jclass clazz) {
std::lock_guard<std::mutex> lockGuard(lock);
return unmountCount;
}
extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
printf("attached\n");
if (vm->GetEnv(reinterpret_cast<void **>(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) {
printf("Could not initialize JVMTI\n");
abort();
}
jvmtiCapabilities capabilities;
memset(&capabilities, 0, sizeof(capabilities));
capabilities.can_support_virtual_threads = 1;
checkJvmti(jvmti->AddCapabilities(&capabilities), "adding capabilities");
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.VirtualThreadEnd = &vthreadEnd;
checkJvmti(jvmti->SetEventCallbacks(&callbacks, (jint)sizeof(callbacks)), "setting callbacks");
checkJvmti(jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VIRTUAL_THREAD_END, nullptr), "enabling vthread end event");
jint extensionCount = 0;
jvmtiExtensionEventInfo* extensions;
checkJvmti(jvmti->GetExtensionEvents(&extensionCount, &extensions), "getting extension events");
jint unmountIndex = -1;
jint mountIndex = -1;
for (int exIndex = 0; exIndex < extensionCount; exIndex++) {
jvmtiExtensionEventInfo &eventInfo = extensions[exIndex];
if (strcmp(eventInfo.id, "com.sun.hotspot.events.VirtualThreadUnmount") == 0) {
unmountIndex = eventInfo.extension_event_index;
} else if (strcmp(eventInfo.id, "com.sun.hotspot.events.VirtualThreadMount") == 0) {
mountIndex = eventInfo.extension_event_index;
}
}
if (unmountIndex == -1 || mountIndex == -1) {
printf("extension events not found.");
abort();
}
checkJvmti(jvmti->SetExtensionEventCallback(unmountIndex, vthreadUnmount), "setting extension callback");
checkJvmti(jvmti->SetEventNotificationMode(JVMTI_ENABLE, static_cast<jvmtiEvent>(unmountIndex), nullptr), "enabling extension event");
checkJvmti(jvmti->SetExtensionEventCallback(mountIndex, vthreadMount), "setting extension callback");
checkJvmti(jvmti->SetEventNotificationMode(JVMTI_ENABLE, static_cast<jvmtiEvent>(mountIndex), nullptr), "enabling extension event");
printf("vthread events enabled\n");
return JVMTI_ERROR_NONE;
}
------------------------------------------------------------------------------------
---------- END SOURCE ----------
FREQUENCY : always
- backported by
-
JDK-8320109 missing JVMTI events from vthreads parked during JVMTI attach
- Resolved
-
JDK-8323386 missing JVMTI events from vthreads parked during JVMTI attach
- Resolved
- relates to
-
JDK-8316283 field watch events are not always posted with -Xcomp option
- Open
-
JDK-8319935 Ensure only one JvmtiThreadState is created for one JavaThread associated with attached native thread
- Resolved
-
JDK-8316250 scaleability: some events not optimized with should_post_xxx functions
- Open
- links to
-
Commit openjdk/jdk21u/1ba77d9b
-
Commit openjdk/jdk/fda142ff
-
Review openjdk/jdk21u/337
-
Review openjdk/jdk/15467