Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(0.43) Virtual Thread Support #73

Merged
merged 8 commits into from
Nov 20, 2023
174 changes: 90 additions & 84 deletions src/java.base/share/classes/java/lang/VirtualThread.java

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. 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.
*
* 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.
*/

#include "jni.h"
#include "jvmti.h"

static jvmtiEnv *jvmti;

JNIEXPORT void JNICALL Java_GetThreadStateTest_init(JNIEnv *env, jclass clazz) {
JavaVM* vm;
jint res;
res = (*env)->GetJavaVM(env, &vm);
if (res != 0) {
(*env)->FatalError(env, "GetJavaVM failed");
} else {
res = (*vm)->GetEnv(vm, (void**)&jvmti, JVMTI_VERSION);
if (res != JNI_OK) {
(*env)->FatalError(env, "GetEnv failed");
}
}
}

JNIEXPORT jint JNICALL Java_GetThreadStateTest_jvmtiState(JNIEnv *env, jclass clazz, jobject thread) {
jvmtiError err;
jint state = 0;
err = (*jvmti)->GetThreadState(jvmti, thread, &state);
if (err != JVMTI_ERROR_NONE) {
(*env)->FatalError(env, "GetThreadState failed");
}
return state;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. 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.
*
* 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.
*/

/**
* @test
* @bug 8312174
* @summary missing JVMTI events from vthreads parked during JVMTI attach
* @requires vm.continuations
* @requires vm.jvmti
* @requires vm.compMode != "Xcomp"
* @run main/othervm/native
* -Djdk.virtualThreadScheduler.parallelism=9
* -Djdk.attach.allowAttachSelf=true -XX:+EnableDynamicAgentLoading VThreadEventTest attach
*/

import com.sun.tools.attach.VirtualMachine;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.LockSupport;
import java.util.List;
import java.util.ArrayList;

/*
* The test uses custom implementation of the CountDownLatch class.
* The reason is we want the state of tested thread to be predictable.
* With java.util.concurrent.CountDownLatch it is not clear what thread state is expected.
*/
class CountDownLatch {
private int count = 0;

CountDownLatch(int count) {
this.count = count;
}

public synchronized void countDown() {
count--;
notify();
}

public synchronized void await() throws InterruptedException {
while (count > 0) {
wait(1);
}
}
}

public class VThreadEventTest {
static final int TCNT1 = 10;
static final int TCNT2 = 4;
static final int TCNT3 = 4;
static final int THREAD_CNT = TCNT1 + TCNT2 + TCNT3;

private static void log(String msg) { System.out.println(msg); }

private static native int threadEndCount();
private static native int threadMountCount();
private static native int threadUnmountCount();

private static volatile boolean attached;
private static boolean failed;
private static List<Thread> test1Threads = new ArrayList(TCNT1);

private static CountDownLatch ready0 = new CountDownLatch(THREAD_CNT);
private static CountDownLatch ready1 = new CountDownLatch(TCNT1);
private static CountDownLatch ready2 = new CountDownLatch(THREAD_CNT);
private static CountDownLatch mready = new CountDownLatch(1);

private static void await(CountDownLatch dumpedLatch) {
try {
dumpedLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

// The test1 vthreads are kept unmounted until interrupted after agent attach.
static final Runnable test1 = () -> {
synchronized (test1Threads) {
test1Threads.add(Thread.currentThread());
}
log("test1 vthread started");
ready0.countDown();
await(mready);
ready1.countDown(); // to guaranty state is not State.WAITING after await(mready)
try {
Thread.sleep(20000); // big timeout to keep unmounted until interrupted
} catch (InterruptedException ex) {
// it is expected, ignore
}
ready2.countDown();
};

// The test2 vthreads are kept mounted until agent attach.
static final Runnable test2 = () -> {
log("test2 vthread started");
ready0.countDown();
await(mready);
while (!attached) {
// keep mounted
}
ready2.countDown();
};

// The test3 vthreads are kept mounted until agent attach.
static final Runnable test3 = () -> {
log("test3 vthread started");
ready0.countDown();
await(mready);
while (!attached) {
// keep mounted
}
LockSupport.parkNanos(10_000_000L); // will cause extra mount and unmount
ready2.countDown();
};

public static void main(String[] args) throws Exception {
if (Runtime.getRuntime().availableProcessors() < 8) {
log("WARNING: test expects at least 8 processors.");
}
try (ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < TCNT1; i++) {
executorService.execute(test1);
}
for (int i = 0; i < TCNT2; i++) {
executorService.execute(test2);
}
for (int i = 0; i < TCNT3; i++) {
executorService.execute(test3);
}
await(ready0);
mready.countDown();
await(ready1); // to guarantee state is not State.TIMED_WAITING after await(mready) in test1()
// wait for test1 threads to reach TIMED_WAITING state in sleep()
for (Thread t : test1Threads) {
Thread.State state = t.getState();
log("DBG: state: " + state);
while (state != Thread.State.TIMED_WAITING) {
Thread.sleep(10);
state = t.getState();
log("DBG: state: " + state);
}
}

VirtualMachine vm = VirtualMachine.attach(String.valueOf(ProcessHandle.current().pid()));
vm.loadAgentLibrary("VThreadEventTest");
Thread.sleep(200); // to allow the agent to get ready

attached = true;
for (Thread t : test1Threads) {
t.interrupt();
}
ready2.await();
}
// wait until all VirtualThreadEnd events have been sent
for (int sleepNo = 1; threadEndCount() < THREAD_CNT; sleepNo++) {
Thread.sleep(100);
if (sleepNo % 100 == 0) { // 10 sec period of waiting
log("main: waited seconds: " + sleepNo/10);
}
}
int threadEndCnt = threadEndCount();
int threadMountCnt = threadMountCount();
int threadUnmountCnt = threadUnmountCount();
int threadEndExp = THREAD_CNT;
int threadMountExp = THREAD_CNT - TCNT2;
int threadUnmountExp = THREAD_CNT + TCNT3;

log("ThreadEnd cnt: " + threadEndCnt + " (expected: " + threadEndExp + ")");
log("ThreadMount cnt: " + threadMountCnt + " (expected: " + threadMountExp + ")");
log("ThreadUnmount cnt: " + threadUnmountCnt + " (expected: " + threadUnmountExp + ")");

if (threadEndCnt != threadEndExp) {
log("FAILED: unexpected count of ThreadEnd events");
failed = true;
}
if (threadMountCnt != threadMountExp) {
log("FAILED: unexpected count of ThreadMount events");
failed = true;
}
if (threadUnmountCnt != threadUnmountExp) {
log("FAILED: unexpected count of ThreadUnmount events");
failed = true;
}
if (failed) {
throw new RuntimeException("FAILED: event count is wrong");
}
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. 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.
*
* 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.
*/
/*
* ===========================================================================
* (c) Copyright IBM Corp. 2023, 2023 All Rights Reserved
* ===========================================================================
*/

#include <cstring>
#include <jvmti.h>
#include <atomic>
#include "jvmti_common.h"

extern "C" {

static jvmtiEnv *jvmti = nullptr;
static std::atomic<int> thread_end_cnt(0);
static std::atomic<int> thread_unmount_cnt(0);
static std::atomic<int> thread_mount_cnt(0);

void JNICALL VirtualThreadEnd(jvmtiEnv *jvmti, JNIEnv* jni, jthread vthread) {
thread_end_cnt++;
}

void JNICALL VirtualThreadMount(jvmtiEnv* jvmti, ...) {
thread_mount_cnt++;
}

void JNICALL VirtualThreadUnmount(jvmtiEnv* jvmti, ...) {
thread_unmount_cnt++;
}

JNIEXPORT jint JNICALL
Java_VThreadEventTest_threadEndCount(JNIEnv* jni, jclass clazz) {
return thread_end_cnt;
}

JNIEXPORT jint JNICALL
Java_VThreadEventTest_threadMountCount(JNIEnv* jni, jclass clazz) {
return thread_mount_cnt;
}

JNIEXPORT jint JNICALL
Java_VThreadEventTest_threadUnmountCount(JNIEnv* jni, jclass clazz) {
return thread_unmount_cnt;
}

JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
jvmtiEventCallbacks callbacks;
jvmtiCapabilities caps;
jvmtiError err;

LOG("Agent_OnAttach started\n");
if (vm->GetEnv(reinterpret_cast<void **>(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) {
LOG("Could not initialize JVMTI env\n");
return JNI_ERR;
}
memset(&caps, 0, sizeof(caps));
caps.can_support_virtual_threads = 1;
check_jvmti_error(jvmti->AddCapabilities(&caps), "AddCapabilities");

memset(&callbacks, 0, sizeof(callbacks));
callbacks.VirtualThreadEnd = &VirtualThreadEnd;

err = jvmti->SetEventCallbacks(&callbacks, (jint)sizeof(callbacks));
check_jvmti_error(err, "SetEventCallbacks");

err = set_ext_event_callback(jvmti, "VirtualThreadMount", VirtualThreadMount);
check_jvmti_error(err, "SetExtEventCallback for VirtualThreadMount");

err = set_ext_event_callback(jvmti, "VirtualThreadUnmount", VirtualThreadUnmount);
check_jvmti_error(err, "SetExtEventCallback for VirtualThreadUnmount");

err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VIRTUAL_THREAD_END, nullptr);
check_jvmti_error(err, "SetEventNotificationMode for VirtualThreadEnd");

err = set_ext_event_notification_mode(jvmti, JVMTI_ENABLE, "VirtualThreadMount", NULL);
check_jvmti_error(err, "SetEventNotificationMode for VirtualThreadMount");

err = set_ext_event_notification_mode(jvmti, JVMTI_ENABLE, "VirtualThreadUnmount", NULL);
check_jvmti_error(err, "SetEventNotificationMode for VirtualThreadUnmount");

LOG("vthread events enabled\n");
return JVMTI_ERROR_NONE;
}

} // extern "C"

Loading