summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Alex Light <allight@google.com> 2017-09-25 17:00:16 -0700
committer Alex Light <allight@google.com> 2017-10-02 15:13:27 -0700
commit54d39dc42630cd83f2d1bec5704805febb894819 (patch)
treea23da52ebe6a98a125929a5ae2dacc87db5f7965
parent848574ca50bb7e2d109608359d1086b3ca6bb4b3 (diff)
Implement JVMTI can_signal_thread capability.
Implements the JVMTI can_signal_thread capability and all associated methods and behaviors. This includes both the StopThread and InterruptThread functions. This CL contains the tests for the previous CL. Test: ./test.py --host -j50 Test: stress --cpu 59 && while ./test/run-test --host 1934; do; done Bug: 62821960 Bug: 34415266 Change-Id: I7b6fc37da0d2673caa993e486f078cf129d74c0f
-rw-r--r--openjdkjvmti/OpenjdkJvmTi.cc10
-rw-r--r--openjdkjvmti/art_jvmti.h2
-rw-r--r--openjdkjvmti/ti_thread.cc61
-rw-r--r--openjdkjvmti/ti_thread.h3
-rw-r--r--test/1934-jvmti-signal-thread/expected.txt27
-rw-r--r--test/1934-jvmti-signal-thread/info.txt3
-rwxr-xr-xtest/1934-jvmti-signal-thread/run17
-rw-r--r--test/1934-jvmti-signal-thread/signal_threads.cc157
-rw-r--r--test/1934-jvmti-signal-thread/src/Main.java21
-rw-r--r--test/1934-jvmti-signal-thread/src/art/Monitors.java344
-rw-r--r--test/1934-jvmti-signal-thread/src/art/Suspension.java30
-rw-r--r--test/1934-jvmti-signal-thread/src/art/Test1934.java260
-rw-r--r--test/1934-jvmti-signal-thread/src/art/Threads.java22
-rw-r--r--test/Android.bp2
-rw-r--r--test/ti-agent/jvmti_helper.cc2
-rw-r--r--test/ti-agent/threads_helper.cc41
16 files changed, 994 insertions, 8 deletions
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
index bac57f9bc6..b30d45ae88 100644
--- a/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -163,18 +163,16 @@ class JvmtiFunctions {
return ThreadUtil::ResumeThreadList(env, request_count, request_list, results);
}
- static jvmtiError StopThread(jvmtiEnv* env,
- jthread thread ATTRIBUTE_UNUSED,
- jobject exception ATTRIBUTE_UNUSED) {
+ static jvmtiError StopThread(jvmtiEnv* env, jthread thread, jobject exception) {
ENSURE_VALID_ENV(env);
ENSURE_HAS_CAP(env, can_signal_thread);
- return ERR(NOT_IMPLEMENTED);
+ return ThreadUtil::StopThread(env, thread, exception);
}
- static jvmtiError InterruptThread(jvmtiEnv* env, jthread thread ATTRIBUTE_UNUSED) {
+ static jvmtiError InterruptThread(jvmtiEnv* env, jthread thread) {
ENSURE_VALID_ENV(env);
ENSURE_HAS_CAP(env, can_signal_thread);
- return ERR(NOT_IMPLEMENTED);
+ return ThreadUtil::InterruptThread(env, thread);
}
static jvmtiError GetThreadInfo(jvmtiEnv* env, jthread thread, jvmtiThreadInfo* info_ptr) {
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
index 10ddfc1fe4..ad405e8571 100644
--- a/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -229,7 +229,7 @@ const jvmtiCapabilities kPotentialCapabilities = {
.can_get_monitor_info = 1,
.can_pop_frame = 0,
.can_redefine_classes = 1,
- .can_signal_thread = 0,
+ .can_signal_thread = 1,
.can_get_source_file_name = 1,
.can_get_line_numbers = 1,
.can_get_source_debug_extension = 1,
diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
index 907b5158c6..9a809df011 100644
--- a/openjdkjvmti/ti_thread.cc
+++ b/openjdkjvmti/ti_thread.cc
@@ -949,4 +949,65 @@ jvmtiError ThreadUtil::ResumeThreadList(jvmtiEnv* env,
return OK;
}
+jvmtiError ThreadUtil::StopThread(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jthread thread,
+ jobject exception) {
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self);
+ art::StackHandleScope<1> hs(self);
+ if (exception == nullptr) {
+ return ERR(INVALID_OBJECT);
+ }
+ art::ObjPtr<art::mirror::Object> obj(soa.Decode<art::mirror::Object>(exception));
+ if (!obj->GetClass()->IsThrowableClass()) {
+ return ERR(INVALID_OBJECT);
+ }
+ art::Handle<art::mirror::Throwable> exc(hs.NewHandle(obj->AsThrowable()));
+ art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_);
+ art::Thread* target = nullptr;
+ jvmtiError err = ERR(INTERNAL);
+ if (!GetAliveNativeThread(thread, soa, &target, &err)) {
+ return err;
+ } else if (target->GetState() == art::ThreadState::kStarting || target->IsStillStarting()) {
+ return ERR(THREAD_NOT_ALIVE);
+ }
+ struct StopThreadClosure : public art::Closure {
+ public:
+ explicit StopThreadClosure(art::Handle<art::mirror::Throwable> except) : exception_(except) { }
+
+ void Run(art::Thread* me) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ // Make sure the thread is prepared to notice the exception.
+ art::Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(me);
+ me->SetAsyncException(exception_.Get());
+ // Wake up the thread if it is sleeping.
+ me->Notify();
+ }
+
+ private:
+ art::Handle<art::mirror::Throwable> exception_;
+ };
+ StopThreadClosure c(exc);
+ if (target->RequestSynchronousCheckpoint(&c)) {
+ return OK;
+ } else {
+ // Something went wrong, probably the thread died.
+ return ERR(THREAD_NOT_ALIVE);
+ }
+}
+
+jvmtiError ThreadUtil::InterruptThread(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread thread) {
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self);
+ art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_);
+ art::Thread* target = nullptr;
+ jvmtiError err = ERR(INTERNAL);
+ if (!GetAliveNativeThread(thread, soa, &target, &err)) {
+ return err;
+ } else if (target->GetState() == art::ThreadState::kStarting || target->IsStillStarting()) {
+ return ERR(THREAD_NOT_ALIVE);
+ }
+ target->Interrupt(self);
+ return OK;
+}
+
} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_thread.h b/openjdkjvmti/ti_thread.h
index ceebff67ec..09b4cabcfc 100644
--- a/openjdkjvmti/ti_thread.h
+++ b/openjdkjvmti/ti_thread.h
@@ -93,6 +93,9 @@ class ThreadUtil {
const jthread* threads,
jvmtiError* results);
+ static jvmtiError StopThread(jvmtiEnv* env, jthread thr, jobject exception);
+ static jvmtiError InterruptThread(jvmtiEnv* env, jthread thr);
+
// Returns true if we decoded the thread and it is alive, false otherwise with an appropriate
// error placed into 'err'. A thread is alive if it has had it's 'start' function called and has
// (or at least could have) executed managed code and has not yet returned past it's first managed
diff --git a/test/1934-jvmti-signal-thread/expected.txt b/test/1934-jvmti-signal-thread/expected.txt
new file mode 100644
index 0000000000..69a0e9e9db
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/expected.txt
@@ -0,0 +1,27 @@
+Interrupt before start
+interrupting other thread before starting
+Caught exception java.lang.RuntimeException: JVMTI_ERROR_THREAD_NOT_ALIVE
+Stop before start
+stopping other thread before starting
+Caught exception java.lang.RuntimeException: JVMTI_ERROR_THREAD_NOT_ALIVE
+Interrupt recur
+Interrupting other thread recurring
+Other thread Interrupted. err: java.lang.Error: Interrupted!
+Stop Recur
+stopping other thread recurring
+Other thread Stopped by: java.lang.Error: AWESOME!
+Interrupt spinning
+Interrupting other thread spinning
+Other thread Interrupted.
+Stop spinning
+stopping other thread spinning
+Other thread Stopped by: java.lang.Error: AWESOME!
+Interrupt wait
+interrupting other thread waiting
+Other thread interrupted. err: java.lang.Error: Interrupted!
+Stop wait
+stopping other thread waiting
+Other thread Stopped by: java.lang.Error: AWESOME
+Stop in native
+stopping other thread
+Other thread Stopped by: java.lang.Error: AWESOME
diff --git a/test/1934-jvmti-signal-thread/info.txt b/test/1934-jvmti-signal-thread/info.txt
new file mode 100644
index 0000000000..c8c91893e5
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that the GetBytecodes function works as expected.
diff --git a/test/1934-jvmti-signal-thread/run b/test/1934-jvmti-signal-thread/run
new file mode 100755
index 0000000000..e92b873956
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-run "$@" --jvmti
diff --git a/test/1934-jvmti-signal-thread/signal_threads.cc b/test/1934-jvmti-signal-thread/signal_threads.cc
new file mode 100644
index 0000000000..726a7a86ae
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/signal_threads.cc
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <pthread.h>
+
+#include <cstdio>
+#include <iostream>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "jni.h"
+#include "jvmti.h"
+
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1934SignalThreads {
+
+struct NativeMonitor {
+ jrawMonitorID continue_monitor;
+ bool should_continue;
+ jrawMonitorID start_monitor;
+ bool should_start;
+};
+
+extern "C" JNIEXPORT jlong JNICALL Java_art_Test1934_allocNativeMonitor(JNIEnv* env, jclass) {
+ NativeMonitor* mon;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->Allocate(sizeof(NativeMonitor),
+ reinterpret_cast<unsigned char**>(&mon)))) {
+ return -1l;
+ }
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->CreateRawMonitor("test-1934 start",
+ &mon->start_monitor))) {
+ return -1l;
+ }
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->CreateRawMonitor("test-1934 continue",
+ &mon->continue_monitor))) {
+ return -1l;
+ }
+ mon->should_continue = false;
+ mon->should_start = false;
+ return static_cast<jlong>(reinterpret_cast<intptr_t>(mon));
+}
+
+extern "C" JNIEXPORT void Java_art_Test1934_nativeWaitForOtherThread(JNIEnv* env,
+ jclass,
+ jlong id) {
+ NativeMonitor* mon = reinterpret_cast<NativeMonitor*>(static_cast<intptr_t>(id));
+ // Start
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorEnter(mon->start_monitor))) {
+ return;
+ }
+ mon->should_start = true;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->RawMonitorNotifyAll(mon->start_monitor))) {
+ JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->start_monitor));
+ return;
+ }
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->start_monitor))) {
+ return;
+ }
+
+ // Finish
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorEnter(mon->continue_monitor))) {
+ return;
+ }
+ while (!mon->should_continue) {
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->RawMonitorWait(mon->continue_monitor, -1l))) {
+ JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->continue_monitor));
+ return;
+ }
+ }
+ JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->continue_monitor));
+}
+
+extern "C" JNIEXPORT void Java_art_Test1934_nativeDoInterleaved(JNIEnv* env,
+ jclass,
+ jlong id,
+ jobject closure) {
+ NativeMonitor* mon = reinterpret_cast<NativeMonitor*>(static_cast<intptr_t>(id));
+ // Wait for start.
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorEnter(mon->start_monitor))) {
+ return;
+ }
+ while (!mon->should_start) {
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->RawMonitorWait(mon->start_monitor, -1l))) {
+ return;
+ }
+ }
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->start_monitor))) {
+ return;
+ }
+
+ // Call closure.
+ ScopedLocalRef<jclass> runnable_klass(env, env->FindClass("java/lang/Runnable"));
+ if (env->ExceptionCheck()) {
+ return;
+ }
+ jmethodID doRun = env->GetMethodID(runnable_klass.get(), "run", "()V");
+ if (env->ExceptionCheck()) {
+ return;
+ }
+ env->CallVoidMethod(closure, doRun);
+
+ // Tell other thread to finish.
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorEnter(mon->continue_monitor))) {
+ return;
+ }
+ mon->should_continue = true;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->RawMonitorNotifyAll(mon->continue_monitor))) {
+ JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->continue_monitor));
+ return;
+ }
+ JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->continue_monitor));
+}
+
+extern "C" JNIEXPORT void Java_art_Test1934_destroyNativeMonitor(JNIEnv*, jclass, jlong id) {
+ NativeMonitor* mon = reinterpret_cast<NativeMonitor*>(static_cast<intptr_t>(id));
+ jvmti_env->DestroyRawMonitor(mon->start_monitor);
+ jvmti_env->DestroyRawMonitor(mon->continue_monitor);
+ jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(mon));
+}
+
+} // namespace Test1934SignalThreads
+} // namespace art
+
diff --git a/test/1934-jvmti-signal-thread/src/Main.java b/test/1934-jvmti-signal-thread/src/Main.java
new file mode 100644
index 0000000000..539763c4ac
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1934.run();
+ }
+}
diff --git a/test/1934-jvmti-signal-thread/src/art/Monitors.java b/test/1934-jvmti-signal-thread/src/art/Monitors.java
new file mode 100644
index 0000000000..7fe2b60c1e
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/src/art/Monitors.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.*;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class Monitors {
+ public native static void setupMonitorEvents(
+ Class<?> method_klass,
+ Method monitor_contended_enter_event,
+ Method monitor_contended_entered_event,
+ Method monitor_wait_event,
+ Method monitor_waited_event,
+ Class<?> lock_klass,
+ Thread thr);
+ public native static void stopMonitorEvents();
+
+ public static class NamedLock {
+ public final String name;
+ private volatile int calledNotify;
+ public NamedLock(String name) {
+ this.name = name;
+ calledNotify = 0;
+ }
+
+ public String toString() {
+ return String.format("NamedLock[%s]", name);
+ }
+
+ public final void DoWait() throws Exception {
+ final int v = calledNotify;
+ while (v == calledNotify) {
+ wait();
+ }
+ }
+
+ public final void DoWait(long t) throws Exception {
+ final int v = calledNotify;
+ final long target = System.currentTimeMillis() + (t / 2);
+ while (v == calledNotify && (t < 0 || System.currentTimeMillis() < target)) {
+ wait(t);
+ }
+ }
+
+ public final void DoNotifyAll() throws Exception {
+ calledNotify++;
+ notifyAll();
+ }
+
+ public final void DoNotify() throws Exception {
+ calledNotify++;
+ notify();
+ }
+ }
+
+ public static final class MonitorUsage {
+ public final Object monitor;
+ public final Thread owner;
+ public final int entryCount;
+ public final Thread[] waiters;
+ public final Thread[] notifyWaiters;
+
+ public MonitorUsage(
+ Object monitor,
+ Thread owner,
+ int entryCount,
+ Thread[] waiters,
+ Thread[] notifyWaiters) {
+ this.monitor = monitor;
+ this.entryCount = entryCount;
+ this.owner = owner;
+ this.waiters = waiters;
+ this.notifyWaiters = notifyWaiters;
+ }
+
+ private static String toNameList(Thread[] ts) {
+ return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray());
+ }
+
+ public String toString() {
+ return String.format(
+ "MonitorUsage{ monitor: %s, owner: %s, entryCount: %d, waiters: %s, notify_waiters: %s }",
+ monitor,
+ (owner != null) ? owner.getName() : "<NULL>",
+ entryCount,
+ toNameList(waiters),
+ toNameList(notifyWaiters));
+ }
+ }
+
+ public static native MonitorUsage getObjectMonitorUsage(Object monitor);
+ public static native Object getCurrentContendedMonitor(Thread thr);
+
+ public static class TestException extends Error {
+ public TestException() { super(); }
+ public TestException(String s) { super(s); }
+ public TestException(String s, Throwable c) { super(s, c); }
+ }
+
+ public static class LockController {
+ private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT }
+
+ public final NamedLock lock;
+ public final long timeout;
+ private final AtomicStampedReference<Action> action;
+ private volatile Thread runner = null;
+ private volatile boolean started = false;
+ private volatile boolean held = false;
+ private static final AtomicInteger cnt = new AtomicInteger(0);
+ private volatile Throwable exe;
+
+ public LockController(NamedLock lock) {
+ this(lock, 10 * 1000);
+ }
+ public LockController(NamedLock lock, long timeout) {
+ this.lock = lock;
+ this.timeout = timeout;
+ this.action = new AtomicStampedReference(Action.HOLD, 0);
+ this.exe = null;
+ }
+
+ public boolean IsWorkerThread(Thread thd) {
+ return Objects.equals(runner, thd);
+ }
+
+ public boolean IsLocked() {
+ checkException();
+ return held;
+ }
+
+ public void checkException() {
+ if (exe != null) {
+ throw new TestException("Exception thrown by other thread!", exe);
+ }
+ }
+
+ private void setAction(Action a) {
+ int stamp = action.getStamp();
+ // Wait for it to be HOLD before updating.
+ while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) {
+ stamp = action.getStamp();
+ }
+ }
+
+ public synchronized void suspendWorker() throws Exception {
+ checkException();
+ if (runner == null) {
+ throw new TestException("We don't have any runner holding " + lock);
+ }
+ Suspension.suspend(runner);
+ }
+
+ public Object getWorkerContendedMonitor() throws Exception {
+ checkException();
+ if (runner == null) {
+ return null;
+ }
+ return getCurrentContendedMonitor(runner);
+ }
+
+ public synchronized void DoLock() {
+ if (IsLocked()) {
+ throw new Error("lock is already acquired or being acquired.");
+ }
+ if (runner != null) {
+ throw new Error("Already have thread!");
+ }
+ runner = new Thread(() -> {
+ started = true;
+ try {
+ synchronized (lock) {
+ held = true;
+ int[] stamp_h = new int[] { -1 };
+ Action cur_action = Action.HOLD;
+ try {
+ while (true) {
+ cur_action = action.get(stamp_h);
+ int stamp = stamp_h[0];
+ if (cur_action == Action.RELEASE) {
+ // The other thread will deal with reseting action.
+ break;
+ }
+ try {
+ switch (cur_action) {
+ case HOLD:
+ Thread.yield();
+ break;
+ case NOTIFY:
+ lock.DoNotify();
+ break;
+ case NOTIFY_ALL:
+ lock.DoNotifyAll();
+ break;
+ case TIMED_WAIT:
+ lock.DoWait(timeout);
+ break;
+ case WAIT:
+ lock.DoWait();
+ break;
+ default:
+ throw new Error("Unknown action " + action);
+ }
+ } finally {
+ // reset action back to hold if it isn't something else.
+ action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1);
+ }
+ }
+ } catch (Exception e) {
+ throw new TestException("Got an error while performing action " + cur_action, e);
+ }
+ }
+ } finally {
+ held = false;
+ started = false;
+ }
+ }, "Locker thread " + cnt.getAndIncrement() + " for " + lock);
+ // Make sure we can get any exceptions this throws.
+ runner.setUncaughtExceptionHandler((t, e) -> { exe = e; });
+ runner.start();
+ }
+
+ public void waitForLockToBeHeld() throws Exception {
+ while (true) {
+ if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) {
+ return;
+ }
+ }
+ }
+
+ public synchronized void waitForNotifySleep() throws Exception {
+ if (runner == null) {
+ throw new Error("No thread trying to lock!");
+ }
+ do {
+ checkException();
+ } while (!started ||
+ !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner));
+ }
+
+ public synchronized void waitForContendedSleep() throws Exception {
+ if (runner == null) {
+ throw new Error("No thread trying to lock!");
+ }
+ do {
+ checkException();
+ } while (!started ||
+ runner.getState() != Thread.State.BLOCKED ||
+ !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner));
+ }
+
+ public synchronized void DoNotify() {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.NOTIFY);
+ }
+
+ public synchronized void DoNotifyAll() {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.NOTIFY_ALL);
+ }
+
+ public synchronized void DoTimedWait() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.TIMED_WAIT);
+ }
+
+ public synchronized void DoWait() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.WAIT);
+ }
+
+ public synchronized void interruptWorker() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ runner.interrupt();
+ }
+
+ public synchronized void waitForActionToFinish() throws Exception {
+ checkException();
+ while (action.getReference() != Action.HOLD) { checkException(); }
+ }
+
+ public synchronized void DoUnlock() throws Exception {
+ Error throwing = null;
+ if (!IsLocked()) {
+ // We might just be racing some exception that was thrown by the worker thread. Cache the
+ // exception, we will throw one from the worker before this one.
+ throwing = new Error("Not locked!");
+ }
+ setAction(Action.RELEASE);
+ Thread run = runner;
+ runner = null;
+ while (held) {}
+ run.join();
+ action.set(Action.HOLD, 0);
+ // Make sure to throw any exception that occurred since it might not have unlocked due to our
+ // request.
+ checkException();
+ DoCleanup();
+ if (throwing != null) {
+ throw throwing;
+ }
+ }
+
+ public synchronized void DoCleanup() throws Exception {
+ if (runner != null) {
+ Thread run = runner;
+ runner = null;
+ while (held) {}
+ run.join();
+ }
+ action.set(Action.HOLD, 0);
+ exe = null;
+ }
+ }
+}
+
diff --git a/test/1934-jvmti-signal-thread/src/art/Suspension.java b/test/1934-jvmti-signal-thread/src/art/Suspension.java
new file mode 100644
index 0000000000..16e62ccac9
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Suspension {
+ // Suspends a thread using jvmti.
+ public native static void suspend(Thread thr);
+
+ // Resumes a thread using jvmti.
+ public native static void resume(Thread thr);
+
+ public native static boolean isSuspended(Thread thr);
+
+ public native static int[] suspendList(Thread... threads);
+ public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1934-jvmti-signal-thread/src/art/Test1934.java b/test/1934-jvmti-signal-thread/src/art/Test1934.java
new file mode 100644
index 0000000000..552570a436
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/src/art/Test1934.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.util.concurrent.Semaphore;
+import java.util.Objects;
+
+public class Test1934 {
+ public static final boolean PRINT_STACK_TRACE = false;
+
+ public static void run() throws Exception {
+ System.out.println("Interrupt before start");
+ testInterruptBeforeStart();
+
+ System.out.println("Stop before start");
+ testStopBeforeStart();
+
+ System.out.println("Interrupt recur");
+ testInterruptRecur();
+
+ System.out.println("Stop Recur");
+ testStopRecur();
+
+ System.out.println("Interrupt spinning");
+ testInterruptSpinning();
+
+ System.out.println("Stop spinning");
+ testStopSpinning();
+
+ System.out.println("Interrupt wait");
+ testInterruptWait();
+
+ System.out.println("Stop wait");
+ testStopWait();
+
+ System.out.println("Stop in native");
+ testStopInNative();
+ }
+
+ public static void testStopBeforeStart() throws Exception {
+ final Throwable[] out_err = new Throwable[] { null, };
+ final Object tst = new Object();
+ Thread target = new Thread(() -> { while (true) { } }, "waiting thread!");
+ target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+ System.out.println("stopping other thread before starting");
+ try {
+ Threads.stopThread(target, new Error("AWESOME"));
+ target.start();
+ target.join();
+ System.out.println("Other thread Stopped by: " + out_err[0]);
+ if (PRINT_STACK_TRACE && out_err[0] != null) {
+ out_err[0].printStackTrace();
+ }
+ } catch (Exception e) {
+ System.out.println("Caught exception " + e);
+ }
+ }
+
+ public static void testInterruptBeforeStart() throws Exception {
+ final Throwable[] out_err = new Throwable[] { null, };
+ final Object tst = new Object();
+ Thread target = new Thread(() -> { while (true) { } }, "waiting thread!");
+ target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+ System.out.println("interrupting other thread before starting");
+ try {
+ Threads.interruptThread(target);
+ target.start();
+ target.join();
+ System.out.println("Other thread interrupted. err: " + out_err[0]);
+ if (PRINT_STACK_TRACE && out_err[0] != null) {
+ out_err[0].printStackTrace();
+ }
+ } catch (Exception e) {
+ System.out.println("Caught exception " + e);
+ }
+ }
+
+ public static void testStopWait() throws Exception {
+ final Throwable[] out_err = new Throwable[] { null, };
+ final Object tst = new Object();
+ final Semaphore sem = new Semaphore(0);
+ Thread target = new Thread(() -> {
+ sem.release();
+ while (true) {
+ try {
+ synchronized (tst) {
+ tst.wait();
+ }
+ } catch (InterruptedException e) { throw new Error("Interrupted!", e); }
+ }
+ }, "waiting thread!");
+ target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+ target.start();
+ sem.acquire();
+ while (!Objects.equals(Monitors.getCurrentContendedMonitor(target), tst)) {}
+ System.out.println("stopping other thread waiting");
+ Threads.stopThread(target, new Error("AWESOME"));
+ target.join();
+ System.out.println("Other thread Stopped by: " + out_err[0]);
+ if (PRINT_STACK_TRACE && out_err[0] != null) {
+ out_err[0].printStackTrace();
+ }
+ }
+
+ public static void testInterruptWait() throws Exception {
+ final Throwable[] out_err = new Throwable[] { null, };
+ final Object tst = new Object();
+ final Semaphore sem = new Semaphore(0);
+ Thread target = new Thread(() -> {
+ sem.release();
+ while (true) {
+ try {
+ synchronized (tst) {
+ tst.wait();
+ }
+ } catch (InterruptedException e) { throw new Error("Interrupted!", e); }
+ }
+ }, "waiting thread!");
+ target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+ target.start();
+ sem.acquire();
+ while (!Objects.equals(Monitors.getCurrentContendedMonitor(target), tst)) {}
+ System.out.println("interrupting other thread waiting");
+ Threads.interruptThread(target);
+ target.join();
+ System.out.println("Other thread interrupted. err: " + out_err[0]);
+ if (PRINT_STACK_TRACE && out_err[0] != null) {
+ out_err[0].printStackTrace();
+ }
+ }
+
+ public static void doNothing() {}
+ public static native long allocNativeMonitor();
+ public static native void nativeWaitForOtherThread(long id);
+ public static native void nativeDoInterleaved(long id, Runnable op);
+ public static native void destroyNativeMonitor(long id);
+ public static void testStopInNative() throws Exception {
+ final Throwable[] out_err = new Throwable[] { null, };
+ final long native_monitor_id = allocNativeMonitor();
+ final Semaphore sem = new Semaphore(0);
+ Thread target = new Thread(() -> {
+ sem.release();
+ nativeWaitForOtherThread(native_monitor_id);
+ // We need to make sure we do something that can get the exception to be actually noticed.
+ doNothing();
+ }, "native waiting thread!");
+ target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+ target.start();
+ sem.acquire();
+ System.out.println("stopping other thread");
+ nativeDoInterleaved(
+ native_monitor_id,
+ () -> { Threads.stopThread(target, new Error("AWESOME")); });
+ target.join();
+ System.out.println("Other thread Stopped by: " + out_err[0]);
+ if (PRINT_STACK_TRACE && out_err[0] != null) {
+ out_err[0].printStackTrace();
+ }
+ destroyNativeMonitor(native_monitor_id);
+ }
+
+ public static void doRecur(Runnable r) {
+ if (r != null) {
+ r.run();
+ }
+ doRecur(r);
+ }
+
+ public static void testStopRecur() throws Exception {
+ final Throwable[] out_err = new Throwable[] { null, };
+ final Semaphore sem = new Semaphore(0);
+ Thread target = new Thread(() -> {
+ sem.release();
+ while (true) {
+ try {
+ doRecur(null);
+ } catch (StackOverflowError e) {}
+ }
+ }, "recuring thread!");
+ target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+ target.start();
+ sem.acquire();
+ System.out.println("stopping other thread recurring");
+ Threads.stopThread(target, new Error("AWESOME!"));
+ target.join();
+ System.out.println("Other thread Stopped by: " + out_err[0]);
+ if (PRINT_STACK_TRACE && out_err[0] != null) {
+ out_err[0].printStackTrace();
+ }
+ }
+
+ public static void testInterruptRecur() throws Exception {
+ final Throwable[] out_err = new Throwable[] { null, };
+ final Semaphore sem = new Semaphore(0);
+ Thread target = new Thread(() -> {
+ sem.release();
+ while (true) {
+ try {
+ doRecur(() -> {
+ if (Thread.currentThread().isInterrupted()) { throw new Error("Interrupted!"); }
+ });
+ } catch (StackOverflowError e) { }
+ }
+ }, "recuring thread!");
+ target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+ target.start();
+ sem.acquire();
+ System.out.println("Interrupting other thread recurring");
+ Threads.interruptThread(target);
+ target.join();
+ System.out.println("Other thread Interrupted. err: " + out_err[0]);
+ if (PRINT_STACK_TRACE && out_err[0] != null) {
+ out_err[0].printStackTrace();
+ }
+ }
+
+ public static void testStopSpinning() throws Exception {
+ final Throwable[] out_err = new Throwable[] { null, };
+ final Semaphore sem = new Semaphore(0);
+ Thread target = new Thread(() -> { sem.release(); while (true) {} }, "Spinning thread!");
+ target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+ target.start();
+ sem.acquire();
+ System.out.println("stopping other thread spinning");
+ Threads.stopThread(target, new Error("AWESOME!"));
+ target.join();
+ System.out.println("Other thread Stopped by: " + out_err[0]);
+ if (PRINT_STACK_TRACE && out_err[0] != null) {
+ out_err[0].printStackTrace();
+ }
+ }
+
+ public static void testInterruptSpinning() throws Exception {
+ final Semaphore sem = new Semaphore(0);
+ Thread target = new Thread(() -> {
+ sem.release();
+ while (!Thread.currentThread().isInterrupted()) { }
+ }, "Spinning thread!");
+ target.start();
+ sem.acquire();
+ System.out.println("Interrupting other thread spinning");
+ Threads.interruptThread(target);
+ target.join();
+ System.out.println("Other thread Interrupted.");
+ }
+}
diff --git a/test/1934-jvmti-signal-thread/src/art/Threads.java b/test/1934-jvmti-signal-thread/src/art/Threads.java
new file mode 100644
index 0000000000..266813b2e3
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/src/art/Threads.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Threads {
+ public static native void interruptThread(Thread t);
+ public static native void stopThread(Thread t, Throwable thr);
+}
diff --git a/test/Android.bp b/test/Android.bp
index d2eccb06bd..6b5a739c2e 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -227,6 +227,7 @@ art_cc_defaults {
"ti-agent/redefinition_helper.cc",
"ti-agent/suspension_helper.cc",
"ti-agent/stack_trace_helper.cc",
+ "ti-agent/threads_helper.cc",
"ti-agent/trace_helper.cc",
"ti-agent/exceptions_helper.cc",
// This is the list of non-special OnLoad things and excludes BCI and anything that depends
@@ -276,6 +277,7 @@ art_cc_defaults {
"1927-exception-event/exception_event.cc",
"1930-monitor-info/monitor.cc",
"1932-monitor-events-misc/monitor_misc.cc",
+ "1934-jvmti-signal-thread/signal_threads.cc",
],
shared_libs: [
"libbase",
diff --git a/test/ti-agent/jvmti_helper.cc b/test/ti-agent/jvmti_helper.cc
index 4ca2d5d9a0..bceaa6b64b 100644
--- a/test/ti-agent/jvmti_helper.cc
+++ b/test/ti-agent/jvmti_helper.cc
@@ -53,7 +53,7 @@ static const jvmtiCapabilities standard_caps = {
.can_get_monitor_info = 1,
.can_pop_frame = 0,
.can_redefine_classes = 1,
- .can_signal_thread = 0,
+ .can_signal_thread = 1,
.can_get_source_file_name = 1,
.can_get_line_numbers = 1,
.can_get_source_debug_extension = 1,
diff --git a/test/ti-agent/threads_helper.cc b/test/ti-agent/threads_helper.cc
new file mode 100644
index 0000000000..f8aafc3a99
--- /dev/null
+++ b/test/ti-agent/threads_helper.cc
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "common_helper.h"
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "jvmti_helper.h"
+#include "scoped_local_ref.h"
+#include "test_env.h"
+
+namespace art {
+namespace common_threads {
+
+extern "C" JNIEXPORT void Java_art_Threads_interruptThread(JNIEnv* env, jclass, jthread thr) {
+ JvmtiErrorToException(env, jvmti_env, jvmti_env->InterruptThread(thr));
+}
+
+extern "C" JNIEXPORT void Java_art_Threads_stopThread(JNIEnv* env,
+ jclass,
+ jthread thr,
+ jobject exception) {
+ JvmtiErrorToException(env, jvmti_env, jvmti_env->StopThread(thr, exception));
+}
+
+} // namespace common_threads
+} // namespace art