diff options
author | 2017-09-25 17:00:16 -0700 | |
---|---|---|
committer | 2017-10-02 15:13:27 -0700 | |
commit | 54d39dc42630cd83f2d1bec5704805febb894819 (patch) | |
tree | a23da52ebe6a98a125929a5ae2dacc87db5f7965 | |
parent | 848574ca50bb7e2d109608359d1086b3ca6bb4b3 (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.cc | 10 | ||||
-rw-r--r-- | openjdkjvmti/art_jvmti.h | 2 | ||||
-rw-r--r-- | openjdkjvmti/ti_thread.cc | 61 | ||||
-rw-r--r-- | openjdkjvmti/ti_thread.h | 3 | ||||
-rw-r--r-- | test/1934-jvmti-signal-thread/expected.txt | 27 | ||||
-rw-r--r-- | test/1934-jvmti-signal-thread/info.txt | 3 | ||||
-rwxr-xr-x | test/1934-jvmti-signal-thread/run | 17 | ||||
-rw-r--r-- | test/1934-jvmti-signal-thread/signal_threads.cc | 157 | ||||
-rw-r--r-- | test/1934-jvmti-signal-thread/src/Main.java | 21 | ||||
-rw-r--r-- | test/1934-jvmti-signal-thread/src/art/Monitors.java | 344 | ||||
-rw-r--r-- | test/1934-jvmti-signal-thread/src/art/Suspension.java | 30 | ||||
-rw-r--r-- | test/1934-jvmti-signal-thread/src/art/Test1934.java | 260 | ||||
-rw-r--r-- | test/1934-jvmti-signal-thread/src/art/Threads.java | 22 | ||||
-rw-r--r-- | test/Android.bp | 2 | ||||
-rw-r--r-- | test/ti-agent/jvmti_helper.cc | 2 | ||||
-rw-r--r-- | test/ti-agent/threads_helper.cc | 41 |
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 |