# HG changeset patch # User sspitsyn # Date 1570669826 0 # Node ID 865c889ce351e1ece20daba64001f6238022cc08 # Parent 36cdb1cab7b032c1bd2e3452993ca14e728716b0 8231595: [TEST] develop a test case for SuspendThreadList including current thread Summary: Extend test coverage for SuspendThreadList Reviewed-by: amenkov, cjplummer, dholmes diff -r 36cdb1cab7b0 -r 865c889ce351 test/hotspot/jtreg/serviceability/jvmti/SuspendWithCurrentThread/SuspendWithCurrentThread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithCurrentThread/SuspendWithCurrentThread.java Thu Oct 10 01:10:26 2019 +0000 @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2019, 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 8231595 + * @summary [TEST] develop a test case for SuspendThreadList including current thread + * @library /test/lib + * @compile SuspendWithCurrentThread.java + * @run main/othervm/native -agentlib:SuspendWithCurrentThread SuspendWithCurrentThread SuspenderIndex=first + * @run main/othervm/native -agentlib:SuspendWithCurrentThread SuspendWithCurrentThread SuspenderIndex=last + */ + +import java.io.PrintStream; + +public class SuspendWithCurrentThread { + private static final String AGENT_LIB = "SuspendWithCurrentThread"; + private static final String SUSPENDER_OPT = "SuspenderIndex="; + private static final int THREADS_COUNT = 10; + + private static void log(String msg) { System.out.println(msg); } + + private static native void registerTestedThreads(Thread[] threads); + private static native boolean checkTestedThreadsSuspended(); + private static native void resumeTestedThreads(); + private static native void releaseTestedThreadsInfo(); + + // The suspender thread index defines the thread which has to suspend + // the tested threads including itself with the JVMTI SuspendThreadList + private static int suspenderIndex; + + public static void main(String args[]) throws Exception { + try { + System.loadLibrary(AGENT_LIB); + log("Loaded library: " + AGENT_LIB); + } catch (UnsatisfiedLinkError ule) { + log("Failed to load library: " + AGENT_LIB); + log("java.library.path: " + System.getProperty("java.library.path")); + throw ule; + } + if (args.length != 1) { + throw new RuntimeException("Main: wrong arguments count: " + args.length + ", expected: 1"); + } + String arg = args[0]; + if (arg.equals(SUSPENDER_OPT + "first")) { + suspenderIndex = 0; + } else if (arg.equals(SUSPENDER_OPT + "last")) { + suspenderIndex = THREADS_COUNT - 1; + } else { + throw new RuntimeException("Main: wrong argument: " + arg + ", expected: SuspenderIndex={first|last}"); + } + log("Main: suspenderIndex: " + suspenderIndex); + + SuspendWithCurrentThread test = new SuspendWithCurrentThread(); + test.run(); + } + + private ThreadToSuspend[] startTestedThreads(int threadsCount) throws RuntimeException { + ThreadToSuspend[]threads = new ThreadToSuspend[threadsCount]; + + // create tested threads + for (int i = 0; i < threads.length; i++) { + threads[i] = new ThreadToSuspend("ThreadToSuspend#" + i, + i == suspenderIndex // isSuspender + ); + } + log("Main: starting tested threads"); + for (int i = 0; i < threads.length; i++) { + threads[i].start(); + if (!threads[i].checkReady()) { + throw new RuntimeException("Main: unable to prepare tested thread: " + threads[i]); + } + } + log("Main: tested threads started"); + + registerTestedThreads(threads); + return threads; + } + + private boolean checkSuspendedStatus() throws RuntimeException { + log("Main: checking all tested threads have been suspended"); + return checkTestedThreadsSuspended(); + } + + /* The test does the following steps: + * - main thread starts several (THREADS_COUNT) ThreadToSuspend tested threads + * - main thread waits for threads to be ready with the thread.checkReady() + * - main thread registers tested threads within the native agent library + * with the native method registerTestedThreads() + * - main thread triggers the suspender tested thread with the + * ThreadToSuspend.setAllThreadsReady() to suspend tested threads + * - suspender thread suspends tested threads including itself with the native + * method suspendTestedThreads() (uses the JVMTI SuspendThreadList function) + * - main thread checks tested threads suspended status with the native method + * checkSuspendedStatus(); the tested threads are expected to have suspended status + * - main thread resumes tested threads with the native method resumeTestedThreads() + * - main thread releases tested threads with the native method releaseTestedThreads() + * - main thread triggers the tested threads to finish with the thread.letFinish() + */ + private void run() throws Exception { + ThreadToSuspend[] threads = null; // tested threads + + log("Main: started"); + try { + threads = startTestedThreads(THREADS_COUNT); + + log("Main: trigger " + threads[suspenderIndex].getName() + + " to suspend all tested threads including itself"); + ThreadToSuspend.setAllThreadsReady(); + + if (!checkSuspendedStatus()) { + throw new RuntimeException("Main: FAILED status returned from checkTestedThreadsSuspended"); + } + + log("Main: resuming all tested threads"); + resumeTestedThreads(); + } finally { + // let threads to finish + for (int i = 0; i < threads.length; i++) { + threads[i].letFinish(); + } + log("Main: tested threads finished"); + } + + // wait for threads to finish + log("Main: joining tested threads"); + try { + for (int i = 0; i < threads.length; i++) { + threads[i].join(); + } + log("Main: tested thread joined"); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + log("Main: releasing tested threads native info"); + releaseTestedThreadsInfo(); + + log("Main: finished"); + } +} + +/* =================================================================== */ + +// tested threads +class ThreadToSuspend extends Thread { + private static void log(String msg) { System.out.println(msg); } + + private static native void init(); + private static native void suspendTestedThreads(); + private static volatile boolean allThreadsReady = false; + + public static void setAllThreadsReady() { + allThreadsReady = true; + } + + private volatile boolean threadReady = false; + private volatile boolean shouldFinish = false; + private boolean isSuspender = false; + + // make thread with specific name + public ThreadToSuspend(String name, boolean isSuspender) { + super(name); + this.isSuspender = isSuspender; + } + + // run thread continuously + public void run() { + boolean needSuspend = true; + + if (isSuspender) { + init(); + } + threadReady = true; + + // run in a loop + while (!shouldFinish) { + if (isSuspender && needSuspend && allThreadsReady) { + log(getName() + ": before suspending all tested threads including myself"); + needSuspend = false; + suspendTestedThreads(); + log(getName() + ": after suspending all tested threads including myself"); + } + } + } + + // check if thread is ready + public boolean checkReady() { + try { + while (!threadReady) { + sleep(1); + } + } catch (InterruptedException e) { + throw new RuntimeException("checkReady: sleep was interrupted\n\t" + e); + } + return threadReady; + } + + // let thread to finish + public void letFinish() { + shouldFinish = true; + } +} diff -r 36cdb1cab7b0 -r 865c889ce351 test/hotspot/jtreg/serviceability/jvmti/SuspendWithCurrentThread/libSuspendWithCurrentThread.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithCurrentThread/libSuspendWithCurrentThread.cpp Thu Oct 10 01:10:26 2019 +0000 @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2019, 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 +#include "jvmti.h" + +extern "C" { + +static jvmtiEnv* jvmti = NULL; +static jthread* threads = NULL; +static jsize threads_count = 0; +static jrawMonitorID agent_monitor = NULL; + +#define LOG(...) \ + do { \ + printf(__VA_ARGS__); \ + printf("\n"); \ + fflush(stdout); \ + } while (0) + +static void +check_jvmti_status(JNIEnv* jni, jvmtiError err, const char* msg) { + if (err != JVMTI_ERROR_NONE) { + LOG("check_jvmti_status: JVMTI function returned error: %d", err); + jni->FatalError(msg); + } +} + +static void +agent_lock(JNIEnv* jni) { + jvmtiError err = jvmti->RawMonitorEnter(agent_monitor); + check_jvmti_status(jni, err, "monitor_enter: error in JVMTI RawMonitorEnter"); +} + +static void +agent_unlock(JNIEnv* jni) { + jvmtiError err = jvmti->RawMonitorExit(agent_monitor); + check_jvmti_status(jni, err, "monitor_exit: error in JVMTI RawMonitorExit"); +} + +JNIEXPORT void JNICALL +Java_SuspendWithCurrentThread_registerTestedThreads(JNIEnv *jni, jclass cls, jobjectArray threadsArr) { + LOG("\nregisterTestedThreads: started"); + threads_count = jni->GetArrayLength(threadsArr); + + jvmtiError err = jvmti->Allocate((threads_count * sizeof(jthread)), + (unsigned char**)&threads); + check_jvmti_status(jni, err, "registerTestedThreads: error in JVMTI Allocate threads array"); + + for (int i = 0; i < threads_count; i++) { + jobject elem = jni->GetObjectArrayElement(threadsArr, i); + threads[i] = (jthread)jni->NewGlobalRef(elem); + } + LOG("registerTestedThreads: finished\n"); +} + +/* This function is executed on the suspender thread, not the Main thread */ +JNIEXPORT void JNICALL +Java_ThreadToSuspend_init(JNIEnv *jni, jclass cls) { + jvmtiError err = jvmti->CreateRawMonitor("Agent monitor", &agent_monitor); + check_jvmti_status(jni, err, "Java_ThreadToSuspend_init: error in JVMTI CreateRawMonitor"); + + // Main thread has to wait for the suspender thread to complete tested threads suspension + agent_lock(jni); +} + +/* This function is executed on the suspender thread which is not Main thread */ +JNIEXPORT void JNICALL +Java_ThreadToSuspend_suspendTestedThreads(JNIEnv *jni, jclass cls) { + jvmtiError* results = NULL; + jvmtiError err; + + LOG("\nsuspendTestedThreads: started"); + err = jvmti->Allocate((threads_count * sizeof(jvmtiError)), + (unsigned char**)&results); + check_jvmti_status(jni, err, "suspendTestedThreads: error in JVMTI Allocate results array"); + + LOG("suspendTestedThreads: before JVMTI SuspendThreadList"); + err = jvmti->SuspendThreadList(threads_count, threads, results); + check_jvmti_status(jni, err, "suspendTestedThreads: error in JVMTI SuspendThreadList"); + + LOG("suspendTestedThreads: check and print SuspendThreadList results:"); + for (int i = 0; i < threads_count; i++) { + LOG(" thread #%d: (%d)", i, (int)results[i]); + check_jvmti_status(jni, results[i], "suspendTestedThreads: error in SuspendThreadList results[i]"); + } + LOG("suspendTestedThreads: finished\n"); + + // Allow the Main thread to inspect the result of tested threads suspension + agent_unlock(jni); + + err = jvmti->Deallocate((unsigned char*)results); + check_jvmti_status(jni, err, "suspendTestedThreads: error in JVMTI Deallocate results"); +} + +JNIEXPORT jboolean JNICALL +Java_SuspendWithCurrentThread_checkTestedThreadsSuspended(JNIEnv *jni, jclass cls) { + LOG("checkTestedThreadsSuspended: started"); + + // Block until the suspender thread competes the tested threads suspension + agent_lock(jni); + agent_unlock(jni); + + for (int i = 0; i < threads_count; i++) { + jint state = 0; + jvmtiError err = jvmti->GetThreadState(threads[i], &state); + check_jvmti_status(jni, err, "checkTestedThreadsSuspended: error in GetThreadState"); + + if ((state & JVMTI_THREAD_STATE_SUSPENDED) == 0) { + LOG("thread #%d has not been suspended yet: " + "# state: (%#x)", i, (int)state); + jni->FatalError("checkTestedThreadsSuspended: error: expected all tested threads suspended"); + } + } + LOG("checkTestedThreadsSuspended: finished\n"); + return JNI_TRUE; +} + +JNIEXPORT void JNICALL +Java_SuspendWithCurrentThread_resumeTestedThreads(JNIEnv *jni, jclass cls) { + jvmtiError* results = NULL; + jvmtiError err; + + LOG("\nresumeTestedThreads: started"); + err = jvmti->Allocate((threads_count * sizeof(jvmtiError)), + (unsigned char**)&results); + check_jvmti_status(jni, err, "resumeTestedThreads: error in JVMTI Allocate results array"); + + LOG("resumeTestedThreads: before JVMTI ResumeThreadList"); + err = jvmti->ResumeThreadList(threads_count, threads, results); + check_jvmti_status(jni, err, "resumeTestedThreads: error in ResumeThreadList"); + + LOG("resumeTestedThreads: check and print ResumeThreadList results:"); + for (int i = 0; i < threads_count; i++) { + LOG(" thread #%d: (%d)", i, (int)results[i]); + check_jvmti_status(jni, results[i], "resumeTestedThreads: error in ResumeThreadList results[i]"); + } + + err = jvmti->Deallocate((unsigned char*)results); + check_jvmti_status(jni, err, "resumeTestedThreads: error in JVMTI Deallocate results"); + + LOG("resumeTestedThreads: finished\n"); +} + +JNIEXPORT void JNICALL +Java_SuspendWithCurrentThread_releaseTestedThreadsInfo(JNIEnv *jni, jclass cls) { + jvmtiError err; + + LOG("\nreleaseTestedThreadsInfo: started"); + err = jvmti->DestroyRawMonitor(agent_monitor); + check_jvmti_status(jni, err, "releaseTestedThreadsInfo: error in JVMTI DestroyRawMonitor"); + + for (int i = 0; i < threads_count; i++) { + if (threads[i] != NULL) { + jni->DeleteGlobalRef(threads[i]); + } + } + err = jvmti->Deallocate((unsigned char*)threads); + check_jvmti_status(jni, err, "releaseTestedThreadsInfo: error in JVMTI Deallocate threads"); + + LOG("releaseTestedThreadsInfo: finished\n"); +} + + +/** Agent library initialization. */ + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + LOG("\nAgent_OnLoad started"); + + // create JVMTI environment + if (jvm->GetEnv((void **) (&jvmti), JVMTI_VERSION) != JNI_OK) { + return JNI_ERR; + } + + // add specific capabilities for suspending thread + jvmtiCapabilities suspendCaps; + memset(&suspendCaps, 0, sizeof(suspendCaps)); + suspendCaps.can_suspend = 1; + + jvmtiError err = jvmti->AddCapabilities(&suspendCaps); + if (err != JVMTI_ERROR_NONE) { + return JNI_ERR; + } + LOG("Agent_OnLoad finished\n"); + return JNI_OK; +} + +}