ADDITIONAL SYSTEM INFORMATION :
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 JDK-8311556, all events seem to be posted for vthreads mounted during attach.
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