--- /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;
+ }
+}
--- /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 <string.h>
+#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;
+}
+
+}