Add Jvmti Suspend/ResumeThread functions
Enable the can_suspend jvmti capability and implement all required
functionality associated with it.
Test: ./test.py --host -j40
Bug: 34415266
Bug: 62821960
Bug: 63579748
Change-Id: I83b92de7f81622e1658114b034918e8295805b6e
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc
index b0394a5..a472b67 100644
--- a/runtime/base/mutex.cc
+++ b/runtime/base/mutex.cc
@@ -68,6 +68,7 @@
Mutex* Locks::thread_suspend_count_lock_ = nullptr;
Mutex* Locks::trace_lock_ = nullptr;
Mutex* Locks::unexpected_signal_lock_ = nullptr;
+Mutex* Locks::user_code_suspension_lock_ = nullptr;
Uninterruptible Roles::uninterruptible_;
ReaderWriterMutex* Locks::jni_globals_lock_ = nullptr;
Mutex* Locks::jni_weak_globals_lock_ = nullptr;
@@ -1029,6 +1030,7 @@
DCHECK(thread_suspend_count_lock_ != nullptr);
DCHECK(trace_lock_ != nullptr);
DCHECK(unexpected_signal_lock_ != nullptr);
+ DCHECK(user_code_suspension_lock_ != nullptr);
DCHECK(dex_lock_ != nullptr);
} else {
// Create global locks in level order from highest lock level to lowest.
@@ -1045,6 +1047,10 @@
} \
current_lock_level = new_level;
+ UPDATE_CURRENT_LOCK_LEVEL(kUserCodeSuspensionLock);
+ DCHECK(user_code_suspension_lock_ == nullptr);
+ user_code_suspension_lock_ = new Mutex("user code suspension lock", current_lock_level);
+
UPDATE_CURRENT_LOCK_LEVEL(kMutatorLock);
DCHECK(mutator_lock_ == nullptr);
mutator_lock_ = new MutatorMutex("mutator lock", current_lock_level);
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index e77d8d7..7a472e7 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -116,6 +116,7 @@
kTraceLock,
kHeapBitmapLock,
kMutatorLock,
+ kUserCodeSuspensionLock,
kInstrumentEntrypointsLock,
kZygoteCreationLock,
@@ -578,6 +579,11 @@
// Guards allocation entrypoint instrumenting.
static Mutex* instrument_entrypoints_lock_;
+ // Guards code that deals with user-code suspension. This mutex must be held when suspending or
+ // resuming threads with SuspendReason::kForUserCode. It may be held by a suspended thread, but
+ // only if the suspension is not due to SuspendReason::kForUserCode.
+ static Mutex* user_code_suspension_lock_ ACQUIRED_AFTER(instrument_entrypoints_lock_);
+
// A barrier is used to synchronize the GC/Debugger thread with mutator threads. When GC/Debugger
// thread wants to suspend all mutator threads, it needs to wait for all mutator threads to pass
// a barrier. Threads that are already suspended will get their barrier passed by the GC/Debugger
@@ -613,7 +619,7 @@
// state is changed | .. running ..
// - if the CAS operation fails then goto x | .. running ..
// .. running .. | .. running ..
- static MutatorMutex* mutator_lock_ ACQUIRED_AFTER(instrument_entrypoints_lock_);
+ static MutatorMutex* mutator_lock_ ACQUIRED_AFTER(user_code_suspension_lock_);
// Allow reader-writer mutual exclusion on the mark and live bitmaps of the heap.
static ReaderWriterMutex* heap_bitmap_lock_ ACQUIRED_AFTER(mutator_lock_);
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index b595112..7fcc28c 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -2480,7 +2480,8 @@
needs_resume = thread->GetDebugSuspendCount() > 0;
}
if (needs_resume) {
- Runtime::Current()->GetThreadList()->Resume(thread, SuspendReason::kForDebugger);
+ bool resumed = Runtime::Current()->GetThreadList()->Resume(thread, SuspendReason::kForDebugger);
+ DCHECK(resumed);
}
}
@@ -3721,7 +3722,9 @@
~ScopedDebuggerThreadSuspension() {
if (other_suspend_) {
- Runtime::Current()->GetThreadList()->Resume(thread_, SuspendReason::kForDebugger);
+ bool resumed = Runtime::Current()->GetThreadList()->Resume(thread_,
+ SuspendReason::kForDebugger);
+ DCHECK(resumed);
}
}
@@ -4043,7 +4046,8 @@
thread_list->UndoDebuggerSuspensions();
} else {
VLOG(jdwp) << " Resuming event thread only";
- thread_list->Resume(targetThread, SuspendReason::kForDebugger);
+ bool resumed = thread_list->Resume(targetThread, SuspendReason::kForDebugger);
+ DCHECK(resumed);
}
return JDWP::ERR_NONE;
diff --git a/runtime/monitor.cc b/runtime/monitor.cc
index 3e3eaae..5c63dca 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -910,7 +910,8 @@
// Go ahead and inflate the lock.
Inflate(self, owner, obj.Get(), hash_code);
}
- thread_list->Resume(owner, SuspendReason::kInternal);
+ bool resumed = thread_list->Resume(owner, SuspendReason::kInternal);
+ DCHECK(resumed);
}
self->SetMonitorEnterObject(nullptr);
}
diff --git a/runtime/native/dalvik_system_VMStack.cc b/runtime/native/dalvik_system_VMStack.cc
index 7d2d0e5..2aeef60 100644
--- a/runtime/native/dalvik_system_VMStack.cc
+++ b/runtime/native/dalvik_system_VMStack.cc
@@ -62,7 +62,8 @@
trace = thread->CreateInternalStackTrace<false>(soa);
}
// Restart suspended thread.
- thread_list->Resume(thread, SuspendReason::kInternal);
+ bool resumed = thread_list->Resume(thread, SuspendReason::kInternal);
+ DCHECK(resumed);
} else if (timed_out) {
LOG(ERROR) << "Trying to get thread's stack failed as the thread failed to suspend within a "
"generous timeout.";
diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc
index 8b76327..4ce72ed 100644
--- a/runtime/native/java_lang_Thread.cc
+++ b/runtime/native/java_lang_Thread.cc
@@ -155,7 +155,8 @@
ScopedObjectAccess soa(env);
thread->SetThreadName(name.c_str());
}
- thread_list->Resume(thread, SuspendReason::kInternal);
+ bool resumed = thread_list->Resume(thread, SuspendReason::kInternal);
+ DCHECK(resumed);
} else if (timed_out) {
LOG(ERROR) << "Trying to set thread name to '" << name.c_str() << "' failed as the thread "
"failed to suspend within a generous timeout.";
diff --git a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
index c516b66..125d737 100644
--- a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
+++ b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
@@ -76,7 +76,8 @@
trace = Thread::InternalStackTraceToStackTraceElementArray(soa, internal_trace);
}
// Restart suspended thread.
- thread_list->Resume(thread, SuspendReason::kInternal);
+ bool resumed = thread_list->Resume(thread, SuspendReason::kInternal);
+ DCHECK(resumed);
} else {
if (timed_out) {
LOG(ERROR) << "Trying to get thread's stack by id failed as the thread failed to suspend "
diff --git a/runtime/openjdkjvm/OpenjdkJvm.cc b/runtime/openjdkjvm/OpenjdkJvm.cc
index 4560dda..6a8f2ce 100644
--- a/runtime/openjdkjvm/OpenjdkJvm.cc
+++ b/runtime/openjdkjvm/OpenjdkJvm.cc
@@ -432,7 +432,8 @@
art::ScopedObjectAccess soa(env);
thread->SetThreadName(name.c_str());
}
- thread_list->Resume(thread, art::SuspendReason::kInternal);
+ bool resumed = thread_list->Resume(thread, art::SuspendReason::kInternal);
+ DCHECK(resumed);
} else if (timed_out) {
LOG(ERROR) << "Trying to set thread name to '" << name.c_str() << "' failed as the thread "
"failed to suspend within a generous timeout.";
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 505e844..4dff660 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -133,34 +133,34 @@
return ThreadUtil::GetAllThreads(env, threads_count_ptr, threads_ptr);
}
- static jvmtiError SuspendThread(jvmtiEnv* env, jthread thread ATTRIBUTE_UNUSED) {
+ static jvmtiError SuspendThread(jvmtiEnv* env, jthread thread) {
ENSURE_VALID_ENV(env);
ENSURE_HAS_CAP(env, can_suspend);
- return ERR(NOT_IMPLEMENTED);
+ return ThreadUtil::SuspendThread(env, thread);
}
static jvmtiError SuspendThreadList(jvmtiEnv* env,
- jint request_count ATTRIBUTE_UNUSED,
- const jthread* request_list ATTRIBUTE_UNUSED,
- jvmtiError* results ATTRIBUTE_UNUSED) {
+ jint request_count,
+ const jthread* request_list,
+ jvmtiError* results) {
ENSURE_VALID_ENV(env);
ENSURE_HAS_CAP(env, can_suspend);
- return ERR(NOT_IMPLEMENTED);
+ return ThreadUtil::SuspendThreadList(env, request_count, request_list, results);
}
- static jvmtiError ResumeThread(jvmtiEnv* env, jthread thread ATTRIBUTE_UNUSED) {
+ static jvmtiError ResumeThread(jvmtiEnv* env, jthread thread) {
ENSURE_VALID_ENV(env);
ENSURE_HAS_CAP(env, can_suspend);
- return ERR(NOT_IMPLEMENTED);
+ return ThreadUtil::ResumeThread(env, thread);
}
static jvmtiError ResumeThreadList(jvmtiEnv* env,
- jint request_count ATTRIBUTE_UNUSED,
- const jthread* request_list ATTRIBUTE_UNUSED,
- jvmtiError* results ATTRIBUTE_UNUSED) {
+ jint request_count,
+ const jthread* request_list,
+ jvmtiError* results) {
ENSURE_VALID_ENV(env);
ENSURE_HAS_CAP(env, can_suspend);
- return ERR(NOT_IMPLEMENTED);
+ return ThreadUtil::ResumeThreadList(env, request_count, request_list, results);
}
static jvmtiError StopThread(jvmtiEnv* env,
diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h
index c63e502..4d5bb95 100644
--- a/runtime/openjdkjvmti/art_jvmti.h
+++ b/runtime/openjdkjvmti/art_jvmti.h
@@ -233,7 +233,7 @@
.can_generate_exception_events = 0,
.can_generate_frame_pop_events = 0,
.can_generate_breakpoint_events = 1,
- .can_suspend = 0,
+ .can_suspend = 1,
.can_redefine_any_class = 0,
.can_get_current_thread_cpu_time = 0,
.can_get_thread_cpu_time = 0,
diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc
index 2cc2a26..3d447dc 100644
--- a/runtime/openjdkjvmti/ti_thread.cc
+++ b/runtime/openjdkjvmti/ti_thread.cc
@@ -433,6 +433,9 @@
if (native_thread->IsInterrupted()) {
jvmti_state |= JVMTI_THREAD_STATE_INTERRUPTED;
}
+ if (native_thread->IsSuspended()) {
+ jvmti_state |= JVMTI_THREAD_STATE_SUSPENDED;
+ }
// Java state is derived from nativeGetState.
// Note: Our implementation assigns "runnable" to suspended. As such, we will have slightly
@@ -605,4 +608,200 @@
return ERR(NONE);
}
+// Suspends the current thread if it has any suspend requests on it.
+static void SuspendCheck(art::Thread* self)
+ REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_) {
+ art::ScopedObjectAccess soa(self);
+ // Really this is only needed if we are in FastJNI and actually have the mutator_lock_ already.
+ self->FullSuspendCheck();
+}
+
+jvmtiError ThreadUtil::SuspendOther(art::Thread* self,
+ jthread target_jthread,
+ art::Thread* target) {
+ // Loop since we need to bail out and try again if we would end up getting suspended while holding
+ // the user_code_suspension_lock_ due to a SuspendReason::kForUserCode. In this situation we
+ // release the lock, wait to get resumed and try again.
+ do {
+ // Suspend ourself if we have any outstanding suspends. This is so we won't suspend due to
+ // another SuspendThread in the middle of suspending something else potentially causing a
+ // deadlock. We need to do this in the loop because if we ended up back here then we had
+ // outstanding SuspendReason::kForUserCode suspensions and we should wait for them to be cleared
+ // before continuing.
+ SuspendCheck(self);
+ art::MutexLock mu(self, *art::Locks::user_code_suspension_lock_);
+ {
+ art::MutexLock thread_list_mu(self, *art::Locks::thread_suspend_count_lock_);
+ // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_ by
+ // a user-code suspension. We retry and do another SuspendCheck to clear this.
+ if (self->GetUserCodeSuspendCount() != 0) {
+ continue;
+ } else if (target->GetUserCodeSuspendCount() != 0) {
+ return ERR(THREAD_SUSPENDED);
+ }
+ }
+ bool timeout = true;
+ while (timeout) {
+ art::ThreadState state = target->GetState();
+ if (state == art::ThreadState::kTerminated || state == art::ThreadState::kStarting) {
+ return ERR(THREAD_NOT_ALIVE);
+ }
+ target = art::Runtime::Current()->GetThreadList()->SuspendThreadByPeer(
+ target_jthread,
+ /* request_suspension */ true,
+ art::SuspendReason::kForUserCode,
+ &timeout);
+ if (target == nullptr && !timeout) {
+ // TODO It would be good to get more information about why exactly the thread failed to
+ // suspend.
+ return ERR(INTERNAL);
+ }
+ }
+ return OK;
+ } while (true);
+ UNREACHABLE();
+}
+
+jvmtiError ThreadUtil::SuspendSelf(art::Thread* self) {
+ CHECK(self == art::Thread::Current());
+ {
+ art::MutexLock mu(self, *art::Locks::user_code_suspension_lock_);
+ art::MutexLock thread_list_mu(self, *art::Locks::thread_suspend_count_lock_);
+ if (self->GetUserCodeSuspendCount() != 0) {
+ // This can only happen if we race with another thread to suspend 'self' and we lose.
+ return ERR(THREAD_SUSPENDED);
+ }
+ // We shouldn't be able to fail this.
+ if (!self->ModifySuspendCount(self, +1, nullptr, art::SuspendReason::kForUserCode)) {
+ // TODO More specific error would be nice.
+ return ERR(INTERNAL);
+ }
+ }
+ // Once we have requested the suspend we actually go to sleep. We need to do this after releasing
+ // the suspend_lock to make sure we can be woken up. This call gains the mutator lock causing us
+ // to go to sleep until we are resumed.
+ SuspendCheck(self);
+ return OK;
+}
+
+jvmtiError ThreadUtil::SuspendThread(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread thread) {
+ art::Thread* self = art::Thread::Current();
+ art::Thread* target;
+ {
+ art::ScopedObjectAccess soa(self);
+ target = GetNativeThread(thread, soa);
+ }
+ if (target == nullptr) {
+ return ERR(INVALID_THREAD);
+ }
+ if (target == self) {
+ return SuspendSelf(self);
+ } else {
+ return SuspendOther(self, thread, target);
+ }
+}
+
+jvmtiError ThreadUtil::ResumeThread(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jthread thread) {
+ if (thread == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ art::Thread* self = art::Thread::Current();
+ art::Thread* target;
+ {
+ // NB This does a SuspendCheck (during thread state change) so we need to make sure we don't
+ // have the 'suspend_lock' locked here.
+ art::ScopedObjectAccess soa(self);
+ target = GetNativeThread(thread, soa);
+ }
+ if (target == nullptr) {
+ return ERR(INVALID_THREAD);
+ } else if (target == self) {
+ // We would have paused until we aren't suspended anymore due to the ScopedObjectAccess so we
+ // can just return THREAD_NOT_SUSPENDED. Unfortunately we cannot do any real DCHECKs about
+ // current state since it's all concurrent.
+ return ERR(THREAD_NOT_SUSPENDED);
+ }
+ // Now that we know we aren't getting suspended ourself (since we have a mutator lock) we lock the
+ // suspend_lock to start suspending.
+ art::MutexLock mu(self, *art::Locks::user_code_suspension_lock_);
+ {
+ // The JVMTI spec requires us to return THREAD_NOT_SUSPENDED if it is alive but we really cannot
+ // tell why resume failed.
+ art::MutexLock thread_list_mu(self, *art::Locks::thread_suspend_count_lock_);
+ if (target->GetUserCodeSuspendCount() == 0) {
+ return ERR(THREAD_NOT_SUSPENDED);
+ }
+ }
+ if (target->GetState() == art::ThreadState::kTerminated) {
+ return ERR(THREAD_NOT_ALIVE);
+ }
+ DCHECK(target != self);
+ if (!art::Runtime::Current()->GetThreadList()->Resume(target, art::SuspendReason::kForUserCode)) {
+ // TODO Give a better error.
+ // This is most likely THREAD_NOT_SUSPENDED but we cannot really be sure.
+ return ERR(INTERNAL);
+ }
+ return OK;
+}
+
+// Suspends all the threads in the list at the same time. Getting this behavior is a little tricky
+// since we can have threads in the list multiple times. This generally doesn't matter unless the
+// current thread is present multiple times. In that case we need to suspend only once and either
+// return the same error code in all the other slots if it failed or return ERR(THREAD_SUSPENDED) if
+// it didn't. We also want to handle the current thread last to make the behavior of the code
+// simpler to understand.
+jvmtiError ThreadUtil::SuspendThreadList(jvmtiEnv* env,
+ jint request_count,
+ const jthread* threads,
+ jvmtiError* results) {
+ if (request_count == 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ } else if (results == nullptr || threads == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ // This is the list of the indexes in 'threads' and 'results' that correspond to the currently
+ // running thread. These indexes we need to handle specially since we need to only actually
+ // suspend a single time.
+ std::vector<jint> current_thread_indexes;
+ art::Thread* self = art::Thread::Current();
+ for (jint i = 0; i < request_count; i++) {
+ {
+ art::ScopedObjectAccess soa(self);
+ if (threads[i] == nullptr || GetNativeThread(threads[i], soa) == self) {
+ current_thread_indexes.push_back(i);
+ continue;
+ }
+ }
+ results[i] = env->SuspendThread(threads[i]);
+ }
+ if (!current_thread_indexes.empty()) {
+ jint first_current_thread_index = current_thread_indexes[0];
+ // Suspend self.
+ jvmtiError res = env->SuspendThread(threads[first_current_thread_index]);
+ results[first_current_thread_index] = res;
+ // Fill in the rest of the error values as appropriate.
+ jvmtiError other_results = (res != OK) ? res : ERR(THREAD_SUSPENDED);
+ for (auto it = ++current_thread_indexes.begin(); it != current_thread_indexes.end(); ++it) {
+ results[*it] = other_results;
+ }
+ }
+ return OK;
+}
+
+jvmtiError ThreadUtil::ResumeThreadList(jvmtiEnv* env,
+ jint request_count,
+ const jthread* threads,
+ jvmtiError* results) {
+ if (request_count == 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ } else if (results == nullptr || threads == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ for (jint i = 0; i < request_count; i++) {
+ results[i] = env->ResumeThread(threads[i]);
+ }
+ return OK;
+}
+
} // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_thread.h b/runtime/openjdkjvmti/ti_thread.h
index 939aea7..57967eb 100644
--- a/runtime/openjdkjvmti/ti_thread.h
+++ b/runtime/openjdkjvmti/ti_thread.h
@@ -35,8 +35,11 @@
#include "jni.h"
#include "jvmti.h"
+#include "base/mutex.h"
+
namespace art {
class ArtField;
+class Thread;
} // namespace art
namespace openjdkjvmti {
@@ -68,7 +71,33 @@
const void* arg,
jint priority);
+ static jvmtiError SuspendThread(jvmtiEnv* env, jthread thread);
+ static jvmtiError ResumeThread(jvmtiEnv* env, jthread thread);
+
+ static jvmtiError SuspendThreadList(jvmtiEnv* env,
+ jint request_count,
+ const jthread* threads,
+ jvmtiError* results);
+ static jvmtiError ResumeThreadList(jvmtiEnv* env,
+ jint request_count,
+ const jthread* threads,
+ jvmtiError* results);
+
private:
+ // We need to make sure only one thread tries to suspend threads at a time so we can get the
+ // 'suspend-only-once' behavior the spec requires. Internally, ART considers suspension to be a
+ // counted state, allowing a single thread to be suspended multiple times by different users. This
+ // makes mapping into the JVMTI idea of thread suspension difficult. We have decided to split the
+ // difference and ensure that JVMTI tries to treat suspension as the boolean flag as much as
+ // possible with the suspend/resume methods but only do best effort. On the other hand
+ // GetThreadState will be totally accurate as much as possible. This means that calling
+ // ResumeThread on a thread that has state JVMTI_THREAD_STATE_SUSPENDED will not necessarily
+ // cause the thread to wake up if the thread is suspended for the debugger or gc or something.
+ static jvmtiError SuspendSelf(art::Thread* self)
+ REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_);
+ static jvmtiError SuspendOther(art::Thread* self, jthread target_jthread, art::Thread* target)
+ REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_);
+
static art::ArtField* context_class_loader_;
};
diff --git a/runtime/suspend_reason.h b/runtime/suspend_reason.h
index 27c4d32..289a1a4 100644
--- a/runtime/suspend_reason.h
+++ b/runtime/suspend_reason.h
@@ -28,6 +28,8 @@
kInternal,
// Suspending for debugger (code in Dbg::*, runtime/jdwp/, etc.).
kForDebugger,
+ // Suspending due to non-runtime, user controlled, code. (For example Thread#Suspend()).
+ kForUserCode,
};
std::ostream& operator<<(std::ostream& os, const SuspendReason& thread);
diff --git a/runtime/thread-inl.h b/runtime/thread-inl.h
index 95608b5..b5a9626 100644
--- a/runtime/thread-inl.h
+++ b/runtime/thread-inl.h
@@ -121,10 +121,20 @@
return false;
}
for (int i = kLockLevelCount - 1; i >= 0; --i) {
- if (i != kMutatorLock && GetHeldMutex(static_cast<LockLevel>(i)) != nullptr) {
+ if (i != kMutatorLock &&
+ i != kUserCodeSuspensionLock &&
+ GetHeldMutex(static_cast<LockLevel>(i)) != nullptr) {
return false;
}
}
+ // Thread autoanalysis isn't able to understand that the GetHeldMutex(...) or AssertHeld means we
+ // have the mutex meaning we need to do this hack.
+ auto is_suspending_for_user_code = [this]() NO_THREAD_SAFETY_ANALYSIS {
+ return tls32_.user_code_suspend_count != 0;
+ };
+ if (GetHeldMutex(kUserCodeSuspensionLock) != nullptr && is_suspending_for_user_code()) {
+ return false;
+ }
return true;
}
@@ -136,8 +146,9 @@
if (check_locks) {
bool bad_mutexes_held = false;
for (int i = kLockLevelCount - 1; i >= 0; --i) {
- // We expect no locks except the mutator_lock_ or thread list suspend thread lock.
- if (i != kMutatorLock) {
+ // We expect no locks except the mutator_lock_. User code suspension lock is OK as long as
+ // we aren't going to be held suspended due to SuspendReason::kForUserCode.
+ if (i != kMutatorLock && i != kUserCodeSuspensionLock) {
BaseMutex* held_mutex = GetHeldMutex(static_cast<LockLevel>(i));
if (held_mutex != nullptr) {
LOG(ERROR) << "holding \"" << held_mutex->GetName()
@@ -146,6 +157,19 @@
}
}
}
+ // Make sure that if we hold the user_code_suspension_lock_ we aren't suspending due to
+ // user_code_suspend_count which would prevent the thread from ever waking up. Thread
+ // autoanalysis isn't able to understand that the GetHeldMutex(...) or AssertHeld means we
+ // have the mutex meaning we need to do this hack.
+ auto is_suspending_for_user_code = [this]() NO_THREAD_SAFETY_ANALYSIS {
+ return tls32_.user_code_suspend_count != 0;
+ };
+ if (GetHeldMutex(kUserCodeSuspensionLock) != nullptr && is_suspending_for_user_code()) {
+ LOG(ERROR) << "suspending due to user-code while holding \""
+ << Locks::user_code_suspension_lock_->GetName() << "\"! Thread would never "
+ << "wake up.";
+ bad_mutexes_held = true;
+ }
if (gAborting == 0) {
CHECK(!bad_mutexes_held);
}
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 36ecd33..6a3f9e7 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -1208,6 +1208,15 @@
Locks::thread_list_lock_->AssertHeld(self);
}
}
+ // User code suspensions need to be checked more closely since they originate from code outside of
+ // the runtime's control.
+ if (UNLIKELY(reason == SuspendReason::kForUserCode)) {
+ Locks::user_code_suspension_lock_->AssertHeld(self);
+ if (UNLIKELY(delta + tls32_.user_code_suspend_count < 0)) {
+ LOG(ERROR) << "attempting to modify suspend count in an illegal way.";
+ return false;
+ }
+ }
if (UNLIKELY(delta < 0 && tls32_.suspend_count <= 0)) {
UnsafeLogFatalForSuspendCount(self, this);
return false;
@@ -1241,6 +1250,9 @@
case SuspendReason::kForDebugger:
tls32_.debug_suspend_count += delta;
break;
+ case SuspendReason::kForUserCode:
+ tls32_.user_code_suspend_count += delta;
+ break;
case SuspendReason::kInternal:
break;
}
diff --git a/runtime/thread.h b/runtime/thread.h
index e785ddc..0128c96 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -228,6 +228,11 @@
return tls32_.suspend_count;
}
+ int GetUserCodeSuspendCount() const REQUIRES(Locks::thread_suspend_count_lock_,
+ Locks::user_code_suspension_lock_) {
+ return tls32_.user_code_suspend_count;
+ }
+
int GetDebugSuspendCount() const REQUIRES(Locks::thread_suspend_count_lock_) {
return tls32_.debug_suspend_count;
}
@@ -1381,7 +1386,7 @@
thread_exit_check_count(0), handling_signal_(false),
is_transitioning_to_runnable(false), ready_for_debug_invoke(false),
debug_method_entry_(false), is_gc_marking(false), weak_ref_access_enabled(true),
- disable_thread_flip_count(0) {
+ disable_thread_flip_count(0), user_code_suspend_count(0) {
}
union StateAndFlags state_and_flags;
@@ -1456,6 +1461,12 @@
// levels of (nested) JNI critical sections the thread is in and is used to detect a nested JNI
// critical section enter.
uint32_t disable_thread_flip_count;
+
+ // How much of 'suspend_count_' is by request of user code, used to distinguish threads
+ // suspended by the runtime from those suspended by user code.
+ // This should have GUARDED_BY(Locks::user_code_suspension_lock_) but auto analysis cannot be
+ // told that AssertHeld should be good enough.
+ int user_code_suspend_count GUARDED_BY(Locks::thread_suspend_count_lock_);
} tls32_;
struct PACKED(8) tls_64bit_sized_values {
diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc
index fc767ed..9c938ff 100644
--- a/runtime/thread_list.cc
+++ b/runtime/thread_list.cc
@@ -828,7 +828,7 @@
}
}
-void ThreadList::Resume(Thread* thread, SuspendReason reason) {
+bool ThreadList::Resume(Thread* thread, SuspendReason reason) {
// This assumes there was an ATRACE_BEGIN when we suspended the thread.
ATRACE_END();
@@ -841,16 +841,23 @@
MutexLock mu(self, *Locks::thread_list_lock_);
// To check IsSuspended.
MutexLock mu2(self, *Locks::thread_suspend_count_lock_);
- DCHECK(thread->IsSuspended());
+ if (UNLIKELY(!thread->IsSuspended())) {
+ LOG(ERROR) << "Resume(" << reinterpret_cast<void*>(thread)
+ << ") thread not suspended";
+ return false;
+ }
if (!Contains(thread)) {
// We only expect threads within the thread-list to have been suspended otherwise we can't
// stop such threads from delete-ing themselves.
LOG(ERROR) << "Resume(" << reinterpret_cast<void*>(thread)
<< ") thread not within thread list";
- return;
+ return false;
}
- bool updated = thread->ModifySuspendCount(self, -1, nullptr, reason);
- DCHECK(updated);
+ if (UNLIKELY(!thread->ModifySuspendCount(self, -1, nullptr, reason))) {
+ LOG(ERROR) << "Resume(" << reinterpret_cast<void*>(thread)
+ << ") could not modify suspend count.";
+ return false;
+ }
}
{
@@ -860,6 +867,7 @@
}
VLOG(threads) << "Resume(" << reinterpret_cast<void*>(thread) << ") complete";
+ return true;
}
static void ThreadSuspendByPeerWarning(Thread* self,
diff --git a/runtime/thread_list.h b/runtime/thread_list.h
index 41c5e32..11f272c 100644
--- a/runtime/thread_list.h
+++ b/runtime/thread_list.h
@@ -65,8 +65,8 @@
void ResumeAll()
REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_)
UNLOCK_FUNCTION(Locks::mutator_lock_);
- void Resume(Thread* thread, SuspendReason reason = SuspendReason::kInternal)
- REQUIRES(!Locks::thread_suspend_count_lock_);
+ bool Resume(Thread* thread, SuspendReason reason = SuspendReason::kInternal)
+ REQUIRES(!Locks::thread_suspend_count_lock_) WARN_UNUSED;
// Suspends all threads and gets exclusive access to the mutator_lock_.
// If long_suspend is true, then other threads who try to suspend will never timeout.
diff --git a/test/1901-get-bytecodes/run b/test/1901-get-bytecodes/run
index c6e62ae..e92b873 100755
--- a/test/1901-get-bytecodes/run
+++ b/test/1901-get-bytecodes/run
@@ -1,6 +1,6 @@
#!/bin/bash
#
-# Copyright 2016 The Android Open Source Project
+# 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.
diff --git a/test/1901-get-bytecodes/src/art/Test1901.java b/test/1901-get-bytecodes/src/art/Test1901.java
index 6940491..9827e3f 100644
--- a/test/1901-get-bytecodes/src/art/Test1901.java
+++ b/test/1901-get-bytecodes/src/art/Test1901.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
diff --git a/test/1902-suspend/expected.txt b/test/1902-suspend/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/1902-suspend/expected.txt
diff --git a/test/1902-suspend/info.txt b/test/1902-suspend/info.txt
new file mode 100644
index 0000000..c49a20f
--- /dev/null
+++ b/test/1902-suspend/info.txt
@@ -0,0 +1,2 @@
+Test basic jvmti Suspend/ResumeThread behavior
+
diff --git a/test/1902-suspend/run b/test/1902-suspend/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1902-suspend/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/1902-suspend/src/Main.java b/test/1902-suspend/src/Main.java
new file mode 100644
index 0000000..0bc7ba1
--- /dev/null
+++ b/test/1902-suspend/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.Test1902.run();
+ }
+}
diff --git a/test/1902-suspend/src/art/Suspension.java b/test/1902-suspend/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1902-suspend/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/1902-suspend/src/art/Test1902.java b/test/1902-suspend/src/art/Test1902.java
new file mode 100644
index 0000000..2bbfacf
--- /dev/null
+++ b/test/1902-suspend/src/art/Test1902.java
@@ -0,0 +1,118 @@
+/*
+ * 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 Test1902 {
+ public static final Object lock = new Object();
+
+ public static volatile boolean OTHER_THREAD_CONTINUE = true;
+ public static volatile boolean OTHER_THREAD_DID_SOMETHING = true;
+ public static volatile boolean OTHER_THREAD_STARTED = false;
+
+ public static class OtherThread implements Runnable {
+ @Override
+ public void run() {
+ OTHER_THREAD_STARTED = true;
+ while (OTHER_THREAD_CONTINUE) {
+ OTHER_THREAD_DID_SOMETHING = true;
+ }
+ }
+ }
+
+ public static void waitFor(long millis) {
+ try {
+ lock.wait(millis);
+ } catch (Exception e) {
+ System.out.println("Unexpected error: " + e);
+ e.printStackTrace();
+ }
+ }
+
+ public static void waitForSuspension(Thread target) {
+ while (!Suspension.isSuspended(target)) {
+ waitFor(100);
+ }
+ }
+
+ public static void waitForStart() {
+ while (!OTHER_THREAD_STARTED) {
+ waitFor(100);
+ }
+ }
+
+
+ public static void run() {
+ synchronized (lock) {
+ Thread other = new Thread(new OtherThread(), "TARGET THREAD");
+ try {
+ other.start();
+
+ waitForStart();
+
+ // Try to resume ourself.
+ try {
+ Suspension.resume(Thread.currentThread());
+ } catch (Exception e) {
+ if (!e.getMessage().equals("JVMTI_ERROR_THREAD_NOT_SUSPENDED")) {
+ System.out.println("incorrect error for resuming a non-suspended thread");
+ }
+ }
+ try {
+ Suspension.resume(other);
+ } catch (Exception e) {
+ if (!e.getMessage().equals("JVMTI_ERROR_THREAD_NOT_SUSPENDED")) {
+ System.out.println("incorrect error for resuming a non-suspended thread");
+ }
+ }
+
+ Suspension.suspend(other);
+ // Wait 1 second for the other thread to suspend.
+ waitForSuspension(other);
+ OTHER_THREAD_DID_SOMETHING = false;
+ // Wait a second to see if anything happens.
+ waitFor(1000);
+
+ if (OTHER_THREAD_DID_SOMETHING) {
+ System.out.println("Looks like other thread did something while suspended!");
+ }
+ // Resume always.
+ Suspension.resume(other);
+
+ // Wait another second.
+ waitFor(1000);
+
+ if (!OTHER_THREAD_DID_SOMETHING) {
+ System.out.println("Doesn't look like the thread unsuspended!");
+ }
+
+ // Stop the other thread.
+ OTHER_THREAD_CONTINUE = false;
+ // Wait for 1 second for it to die.
+ other.join(1000);
+
+ if (other.isAlive()) {
+ System.out.println("other thread didn't terminate in a reasonable time!");
+ Runtime.getRuntime().halt(1);
+ }
+ } catch (Throwable t) {
+ System.out.println("something was thrown. Runtime might be in unrecoverable state: " + t);
+ t.printStackTrace();
+ Runtime.getRuntime().halt(2);
+ }
+ }
+ }
+}
diff --git a/test/1903-suspend-self/expected.txt b/test/1903-suspend-self/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/1903-suspend-self/expected.txt
diff --git a/test/1903-suspend-self/info.txt b/test/1903-suspend-self/info.txt
new file mode 100644
index 0000000..779becc
--- /dev/null
+++ b/test/1903-suspend-self/info.txt
@@ -0,0 +1 @@
+Test jvmti suspend/resume of the current thread.
diff --git a/test/1903-suspend-self/run b/test/1903-suspend-self/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1903-suspend-self/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/1903-suspend-self/src/Main.java b/test/1903-suspend-self/src/Main.java
new file mode 100644
index 0000000..bd2028f
--- /dev/null
+++ b/test/1903-suspend-self/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.Test1903.run();
+ }
+}
diff --git a/test/1903-suspend-self/src/art/Suspension.java b/test/1903-suspend-self/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1903-suspend-self/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/1903-suspend-self/src/art/Test1903.java b/test/1903-suspend-self/src/art/Test1903.java
new file mode 100644
index 0000000..cf2a55c
--- /dev/null
+++ b/test/1903-suspend-self/src/art/Test1903.java
@@ -0,0 +1,91 @@
+/*
+ * 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 Test1903 {
+ public static final Object lock = new Object();
+
+ public static volatile boolean OTHER_THREAD_CONTINUE = true;
+ public static volatile boolean OTHER_THREAD_DID_SOMETHING = true;
+ public static volatile boolean OTHER_THREAD_STARTED = false;
+ public static volatile boolean OTHER_THREAD_RESUMED = false;
+
+ public static class OtherThread implements Runnable {
+ @Override
+ public void run() {
+ // Wake up main thread.
+ OTHER_THREAD_STARTED = true;
+ try {
+ Suspension.suspend(Thread.currentThread());
+ OTHER_THREAD_RESUMED = true;
+ } catch (Throwable t) {
+ System.out.println("Unexpected error occurred " + t);
+ t.printStackTrace();
+ Runtime.getRuntime().halt(2);
+ }
+ }
+ }
+
+ public static void waitFor(long millis) {
+ try {
+ lock.wait(millis);
+ } catch (Exception e) {
+ System.out.println("Unexpected error: " + e);
+ e.printStackTrace();
+ }
+ }
+
+ public static void waitForSuspension(Thread target) {
+ while (!Suspension.isSuspended(target)) {
+ waitFor(100);
+ }
+ }
+
+ public static void waitForStart() {
+ while (!OTHER_THREAD_STARTED) {
+ waitFor(100);
+ }
+ }
+
+ public static void run() {
+ synchronized (lock) {
+ Thread other = new Thread(new OtherThread(), "TARGET THREAD");
+ try {
+ other.start();
+
+ // Wait for the other thread to actually start doing things.
+
+ waitForStart();
+ waitForSuspension(other);
+
+ Suspension.resume(other);
+ for (int i = 0; i < 1000; i++) {
+ waitFor(100);
+ if (OTHER_THREAD_RESUMED) {
+ return;
+ }
+ }
+ System.out.println("Failed to resume thread!");
+ Runtime.getRuntime().halt(4);
+ } catch (Throwable t) {
+ System.out.println("something was thrown. Runtime might be in unrecoverable state: " + t);
+ t.printStackTrace();
+ Runtime.getRuntime().halt(2);
+ }
+ }
+ }
+}
diff --git a/test/1904-double-suspend/expected.txt b/test/1904-double-suspend/expected.txt
new file mode 100644
index 0000000..321b8a3
--- /dev/null
+++ b/test/1904-double-suspend/expected.txt
@@ -0,0 +1 @@
+Got exception JVMTI_ERROR_THREAD_SUSPENDED
diff --git a/test/1904-double-suspend/info.txt b/test/1904-double-suspend/info.txt
new file mode 100644
index 0000000..5d2415b
--- /dev/null
+++ b/test/1904-double-suspend/info.txt
@@ -0,0 +1 @@
+Test jvmti suspending a thread more than once.
diff --git a/test/1904-double-suspend/run b/test/1904-double-suspend/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1904-double-suspend/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/1904-double-suspend/src/Main.java b/test/1904-double-suspend/src/Main.java
new file mode 100644
index 0000000..a0e71c6
--- /dev/null
+++ b/test/1904-double-suspend/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.Test1904.run();
+ }
+}
diff --git a/test/1904-double-suspend/src/art/Suspension.java b/test/1904-double-suspend/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1904-double-suspend/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/1904-double-suspend/src/art/Test1904.java b/test/1904-double-suspend/src/art/Test1904.java
new file mode 100644
index 0000000..8a52aa0
--- /dev/null
+++ b/test/1904-double-suspend/src/art/Test1904.java
@@ -0,0 +1,109 @@
+/*
+ * 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 Test1904 {
+ public static final Object lock = new Object();
+
+ public static volatile boolean OTHER_THREAD_CONTINUE = true;
+ public static volatile boolean OTHER_THREAD_DID_SOMETHING = true;
+ public static volatile boolean OTHER_THREAD_STARTED = false;
+
+ public static class OtherThread implements Runnable {
+ @Override
+ public void run() {
+ OTHER_THREAD_STARTED = true;
+ while (OTHER_THREAD_CONTINUE) {
+ OTHER_THREAD_DID_SOMETHING = true;
+ }
+ }
+ }
+
+ public static void waitFor(long millis) {
+ try {
+ lock.wait(millis);
+ } catch (Exception e) {
+ System.out.println("Unexpected error: " + e);
+ e.printStackTrace();
+ }
+ }
+
+ public static void waitForSuspension(Thread target) {
+ while (!Suspension.isSuspended(target)) {
+ waitFor(100);
+ }
+ }
+
+ public static void waitForStart() {
+ while (!OTHER_THREAD_STARTED) {
+ waitFor(100);
+ }
+ }
+
+
+ public static void run() {
+ synchronized (lock) {
+ Thread other = new Thread(new OtherThread(), "TARGET THREAD");
+ try {
+ other.start();
+
+ waitForStart();
+
+ Suspension.suspend(other);
+
+ waitForSuspension(other);
+ OTHER_THREAD_DID_SOMETHING = false;
+ // Wait a second to see if anything happens.
+ waitFor(1000);
+
+ if (OTHER_THREAD_DID_SOMETHING) {
+ System.out.println("Looks like other thread did something while suspended!");
+ }
+
+ try {
+ Suspension.suspend(other);
+ } catch (Exception e) {
+ System.out.println("Got exception " + e.getMessage());
+ }
+
+ // Resume always.
+ Suspension.resume(other);
+
+ // Wait another second.
+ waitFor(1000);
+
+ if (!OTHER_THREAD_DID_SOMETHING) {
+ System.out.println("Doesn't look like the thread unsuspended!");
+ }
+
+ // Stop the other thread.
+ OTHER_THREAD_CONTINUE = false;
+ // Wait for 1 second for it to die.
+ other.join(1000);
+
+ if (other.isAlive()) {
+ System.out.println("other thread didn't terminate in a reasonable time!");
+ Runtime.getRuntime().halt(1);
+ }
+ } catch (Throwable t) {
+ System.out.println("something was thrown. Runtime might be in unrecoverable state: " + t);
+ t.printStackTrace();
+ Runtime.getRuntime().halt(2);
+ }
+ }
+ }
+}
diff --git a/test/1905-suspend-native/expected.txt b/test/1905-suspend-native/expected.txt
new file mode 100644
index 0000000..43b2669
--- /dev/null
+++ b/test/1905-suspend-native/expected.txt
@@ -0,0 +1,8 @@
+Resumer: isNativeThreadSpinning() = true
+Resumer: isSuspended(spinner) = false
+Resumer: Suspended spinner while native spinning
+Resumer: isNativeThreadSpinning() = true
+Resumer: isSuspended(spinner) = true
+Resumer: resumed spinner while native spinning
+Resumer: isNativeThreadSpinning() = true
+Resumer: isSuspended(spinner) = false
diff --git a/test/1905-suspend-native/info.txt b/test/1905-suspend-native/info.txt
new file mode 100644
index 0000000..3545d59
--- /dev/null
+++ b/test/1905-suspend-native/info.txt
@@ -0,0 +1 @@
+Tests jvmti suspending of a thread that is spinning in native code.
diff --git a/test/1905-suspend-native/native_suspend.cc b/test/1905-suspend-native/native_suspend.cc
new file mode 100644
index 0000000..95b8da2
--- /dev/null
+++ b/test/1905-suspend-native/native_suspend.cc
@@ -0,0 +1,51 @@
+/*
+ * 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 <atomic>
+
+#include "android-base/logging.h"
+#include "jni.h"
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
+
+#include "jvmti.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1905NativeSuspend {
+
+std::atomic<bool> done(false);
+std::atomic<bool> started(false);
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1905_nativeSpin(JNIEnv*, jclass) {
+ while (!done.load()) {
+ started.store(true);
+ }
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_art_Test1905_isNativeThreadSpinning(JNIEnv*, jclass) {
+ return started.load();
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1905_nativeResume(JNIEnv*, jclass) {
+ done.store(true);
+}
+
+} // namespace Test1905NativeSuspend
+} // namespace art
diff --git a/test/1905-suspend-native/run b/test/1905-suspend-native/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1905-suspend-native/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/1905-suspend-native/src/Main.java b/test/1905-suspend-native/src/Main.java
new file mode 100644
index 0000000..42c02d0
--- /dev/null
+++ b/test/1905-suspend-native/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.Test1905.run();
+ }
+}
diff --git a/test/1905-suspend-native/src/art/Suspension.java b/test/1905-suspend-native/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1905-suspend-native/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/1905-suspend-native/src/art/Test1905.java b/test/1905-suspend-native/src/art/Test1905.java
new file mode 100644
index 0000000..ec39019
--- /dev/null
+++ b/test/1905-suspend-native/src/art/Test1905.java
@@ -0,0 +1,60 @@
+/*
+ * 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 Test1905 {
+ public static void run() throws Exception {
+ final Thread spinner = new Thread(() -> {
+ nativeSpin();
+ }, "Spinner");
+
+ final Thread resumer = new Thread(() -> {
+ String me = Thread.currentThread().getName();
+
+ // wait for the other thread to start spinning.
+ while (!isNativeThreadSpinning()) { }
+
+ System.out.println(me + ": isNativeThreadSpinning() = " + isNativeThreadSpinning());
+ System.out.println(me + ": isSuspended(spinner) = " + Suspension.isSuspended(spinner));
+
+ // Suspend it from java.
+ Suspension.suspend(spinner);
+
+ System.out.println(me + ": Suspended spinner while native spinning");
+ System.out.println(me + ": isNativeThreadSpinning() = " + isNativeThreadSpinning());
+ System.out.println(me + ": isSuspended(spinner) = " + Suspension.isSuspended(spinner));
+
+ // Resume it from java. It is still native spinning.
+ Suspension.resume(spinner);
+
+ System.out.println(me + ": resumed spinner while native spinning");
+ System.out.println(me + ": isNativeThreadSpinning() = " + isNativeThreadSpinning());
+ System.out.println(me + ": isSuspended(spinner) = " + Suspension.isSuspended(spinner));
+ nativeResume();
+ }, "Resumer");
+
+ spinner.start();
+ resumer.start();
+
+ spinner.join();
+ resumer.join();
+ }
+
+ public static native void nativeSpin();
+ public static native void nativeResume();
+ public static native boolean isNativeThreadSpinning();
+}
diff --git a/test/1906-suspend-list-me-first/expected.txt b/test/1906-suspend-list-me-first/expected.txt
new file mode 100644
index 0000000..503d728
--- /dev/null
+++ b/test/1906-suspend-list-me-first/expected.txt
@@ -0,0 +1 @@
+Second thread suspended before first thread suspended self!
diff --git a/test/1906-suspend-list-me-first/info.txt b/test/1906-suspend-list-me-first/info.txt
new file mode 100644
index 0000000..2b2f4e1
--- /dev/null
+++ b/test/1906-suspend-list-me-first/info.txt
@@ -0,0 +1 @@
+Test jvmti SuspendThreadList with the current thread as the first thread in the list.
diff --git a/test/1906-suspend-list-me-first/run b/test/1906-suspend-list-me-first/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1906-suspend-list-me-first/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/1906-suspend-list-me-first/src/Main.java b/test/1906-suspend-list-me-first/src/Main.java
new file mode 100644
index 0000000..1c8432c
--- /dev/null
+++ b/test/1906-suspend-list-me-first/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.Test1906.run();
+ }
+}
diff --git a/test/1906-suspend-list-me-first/src/art/Suspension.java b/test/1906-suspend-list-me-first/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1906-suspend-list-me-first/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/1906-suspend-list-me-first/src/art/Test1906.java b/test/1906-suspend-list-me-first/src/art/Test1906.java
new file mode 100644
index 0000000..9bb272e
--- /dev/null
+++ b/test/1906-suspend-list-me-first/src/art/Test1906.java
@@ -0,0 +1,89 @@
+/*
+ * 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 Test1906 {
+ public static final Object lock = new Object();
+
+ public static volatile boolean SECOND_THREAD_RUN = true;
+ public static volatile boolean SECOND_THREAD_RUNNING = false;
+
+ public static void waitFor(long millis) {
+ try {
+ lock.wait(millis);
+ } catch (Exception e) {
+ System.out.println("Unexpected error: " + e);
+ e.printStackTrace();
+ }
+ }
+
+ public static void waitForSuspension(Thread target) {
+ while (!Suspension.isSuspended(target)) {
+ waitFor(100);
+ }
+ }
+
+ public static void run() {
+ synchronized (lock) {
+ final Thread second_thread = new Thread(
+ () -> {
+ while (SECOND_THREAD_RUN) { SECOND_THREAD_RUNNING = true; }
+ },
+ "SECONDARY THREAD");
+ Thread self_thread = new Thread(
+ () -> {
+ try {
+ // Wait for second thread to start doing stuff.
+ while (!SECOND_THREAD_RUNNING) { }
+ Suspension.suspendList(Thread.currentThread(), second_thread);
+ } catch (Throwable t) {
+ System.out.println("Unexpected error occurred " + t);
+ t.printStackTrace();
+ Runtime.getRuntime().halt(2);
+ }
+ },
+ "TARGET THREAD");
+ try {
+ second_thread.start();
+ self_thread.start();
+
+ waitForSuspension(self_thread);
+
+ // Wait to see if second thread is running.
+ SECOND_THREAD_RUNNING = false;
+ waitFor(1000);
+
+ if (SECOND_THREAD_RUNNING) {
+ System.out.println("Second thread running after first thread suspended self!");
+ } else {
+ System.out.println("Second thread suspended before first thread suspended self!");
+ }
+
+ Suspension.resume(self_thread);
+ waitForSuspension(second_thread);
+ Suspension.resume(second_thread);
+ self_thread.join();
+ SECOND_THREAD_RUN = false;
+ second_thread.join();
+ } catch (Throwable t) {
+ System.out.println("something was thrown. Runtime might be in unrecoverable state: " + t);
+ t.printStackTrace();
+ Runtime.getRuntime().halt(2);
+ }
+ }
+ }
+}
diff --git a/test/1907-suspend-list-self-twice/expected.txt b/test/1907-suspend-list-self-twice/expected.txt
new file mode 100644
index 0000000..cd9b53f
--- /dev/null
+++ b/test/1907-suspend-list-self-twice/expected.txt
@@ -0,0 +1,2 @@
+Suspend self twice returned: [0, 14]
+Thread was no longer suspended after one resume.
diff --git a/test/1907-suspend-list-self-twice/info.txt b/test/1907-suspend-list-self-twice/info.txt
new file mode 100644
index 0000000..923c545
--- /dev/null
+++ b/test/1907-suspend-list-self-twice/info.txt
@@ -0,0 +1 @@
+Test jvmti SuspendThreadList with the current thread on it twice.
diff --git a/test/1907-suspend-list-self-twice/run b/test/1907-suspend-list-self-twice/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1907-suspend-list-self-twice/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/1907-suspend-list-self-twice/src/Main.java b/test/1907-suspend-list-self-twice/src/Main.java
new file mode 100644
index 0000000..910848a
--- /dev/null
+++ b/test/1907-suspend-list-self-twice/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.Test1907.run();
+ }
+}
diff --git a/test/1907-suspend-list-self-twice/src/art/Suspension.java b/test/1907-suspend-list-self-twice/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1907-suspend-list-self-twice/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/1907-suspend-list-self-twice/src/art/Test1907.java b/test/1907-suspend-list-self-twice/src/art/Test1907.java
new file mode 100644
index 0000000..504f7f3
--- /dev/null
+++ b/test/1907-suspend-list-self-twice/src/art/Test1907.java
@@ -0,0 +1,82 @@
+/*
+ * 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.Arrays;
+
+public class Test1907 {
+ public static final Object lock = new Object();
+
+ public static void waitFor(long millis) {
+ try {
+ lock.wait(millis);
+ } catch (Exception e) {
+ System.out.println("Unexpected error: " + e);
+ e.printStackTrace();
+ }
+ }
+
+ public static void waitForSuspension(Thread target) {
+ while (!Suspension.isSuspended(target)) {
+ waitFor(100);
+ }
+ }
+
+ public static void run() {
+ synchronized (lock) {
+ Thread thrd = new Thread(
+ () -> {
+ try {
+ // Put self twice in the suspend list
+ System.out.println("Suspend self twice returned: " +
+ Arrays.toString(
+ Suspension.suspendList(Thread.currentThread(), Thread.currentThread())));
+ } catch (Throwable t) {
+ System.out.println("Unexpected error occurred " + t);
+ t.printStackTrace();
+ Runtime.getRuntime().halt(2);
+ }
+ },
+ "TARGET THREAD");
+ try {
+ thrd.start();
+
+ // Wait for at least one suspend to happen.
+ waitForSuspension(thrd);
+
+ // Wake it up.
+ Suspension.resume(thrd);
+ waitFor(1000);
+
+ // Is it suspended.
+ if (Suspension.isSuspended(thrd)) {
+ Suspension.resume(thrd);
+ thrd.join();
+ System.out.println("Thread was still suspended after one resume.");
+ } else {
+ thrd.join();
+ System.out.println("Thread was no longer suspended after one resume.");
+ }
+
+ } catch (Throwable t) {
+ System.out.println("something was thrown. Runtime might be in unrecoverable state: " + t);
+ t.printStackTrace();
+ Runtime.getRuntime().halt(2);
+ }
+ }
+ }
+}
diff --git a/test/1908-suspend-native-resume-self/expected.txt b/test/1908-suspend-native-resume-self/expected.txt
new file mode 100644
index 0000000..13cc517
--- /dev/null
+++ b/test/1908-suspend-native-resume-self/expected.txt
@@ -0,0 +1,10 @@
+Resumer: isNativeThreadSpinning() = true
+Resumer: isSuspended(spinner) = false
+Resumer: Suspended spinner while native spinning
+Resumer: isNativeThreadSpinning() = true
+Resumer: isSuspended(spinner) = true
+Resuming other thread
+other thread attempting self resume
+Resumer: isSuspended(spinner) = true
+real resume
+other thread resumed.
diff --git a/test/1908-suspend-native-resume-self/info.txt b/test/1908-suspend-native-resume-self/info.txt
new file mode 100644
index 0000000..3545d59
--- /dev/null
+++ b/test/1908-suspend-native-resume-self/info.txt
@@ -0,0 +1 @@
+Tests jvmti suspending of a thread that is spinning in native code.
diff --git a/test/1908-suspend-native-resume-self/native_suspend_resume.cc b/test/1908-suspend-native-resume-self/native_suspend_resume.cc
new file mode 100644
index 0000000..158b22c
--- /dev/null
+++ b/test/1908-suspend-native-resume-self/native_suspend_resume.cc
@@ -0,0 +1,67 @@
+/*
+ * 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 <atomic>
+
+#include "android-base/logging.h"
+#include "jni.h"
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
+
+#include "jvmti.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1908NativeSuspendResume {
+
+std::atomic<bool> done(false);
+std::atomic<bool> started(false);
+std::atomic<bool> resumed(false);
+std::atomic<bool> resuming(false);
+
+extern "C" JNIEXPORT jint JNICALL Java_art_Test1908_nativeSpinAndResume(JNIEnv*,
+ jclass,
+ jthread thr) {
+ while (!done.load()) {
+ started.store(true);
+ }
+ resuming.store(true);
+ jint ret = jvmti_env->ResumeThread(thr);
+ resumed.store(true);
+ return ret;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_art_Test1908_isNativeThreadSpinning(JNIEnv*, jclass) {
+ return started.load();
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1908_waitForNativeResumeStarted(JNIEnv*, jclass) {
+ while (!resuming.load()) {}
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1908_waitForNativeResumeFinished(JNIEnv*, jclass) {
+ while (!resumed.load()) {}
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1908_nativeResume(JNIEnv*, jclass) {
+ done.store(true);
+}
+
+} // namespace Test1908NativeSuspendResume
+} // namespace art
diff --git a/test/1908-suspend-native-resume-self/run b/test/1908-suspend-native-resume-self/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1908-suspend-native-resume-self/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/1908-suspend-native-resume-self/src/Main.java b/test/1908-suspend-native-resume-self/src/Main.java
new file mode 100644
index 0000000..312adc4
--- /dev/null
+++ b/test/1908-suspend-native-resume-self/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.Test1908.run();
+ }
+}
diff --git a/test/1908-suspend-native-resume-self/src/art/Suspension.java b/test/1908-suspend-native-resume-self/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1908-suspend-native-resume-self/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/1908-suspend-native-resume-self/src/art/Test1908.java b/test/1908-suspend-native-resume-self/src/art/Test1908.java
new file mode 100644
index 0000000..9b7020a
--- /dev/null
+++ b/test/1908-suspend-native-resume-self/src/art/Test1908.java
@@ -0,0 +1,71 @@
+/*
+ * 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 Test1908 {
+ public static void run() throws Exception {
+ final Thread spinner = new Thread(() -> {
+ int ret = nativeSpinAndResume(Thread.currentThread());
+ if (ret != 13) {
+ System.out.println("Got " + ret + " instead of JVMTI_ERROR_THREAD_NOT_SUSPENDED");
+ }
+ }, "Spinner");
+
+ final Thread resumer = new Thread(() -> {
+ String me = Thread.currentThread().getName();
+
+ // wait for the other thread to start spinning.
+ while (!isNativeThreadSpinning()) { }
+
+ System.out.println(me + ": isNativeThreadSpinning() = " + isNativeThreadSpinning());
+ System.out.println(me + ": isSuspended(spinner) = " + Suspension.isSuspended(spinner));
+
+ // Suspend it from java.
+ Suspension.suspend(spinner);
+
+ System.out.println(me + ": Suspended spinner while native spinning");
+ System.out.println(me + ": isNativeThreadSpinning() = " + isNativeThreadSpinning());
+ System.out.println(me + ": isSuspended(spinner) = " + Suspension.isSuspended(spinner));
+
+ System.out.println("Resuming other thread");
+ nativeResume();
+ waitForNativeResumeStarted();
+ // Wait for the other thread to try to resume itself
+ try { Thread.currentThread().sleep(1000); } catch (Exception e) {}
+
+ System.out.println("other thread attempting self resume");
+ System.out.println(me + ": isSuspended(spinner) = " + Suspension.isSuspended(spinner));
+
+ System.out.println("real resume");
+ Suspension.resume(spinner);
+ waitForNativeResumeFinished();
+ System.out.println("other thread resumed.");
+ }, "Resumer");
+
+ spinner.start();
+ resumer.start();
+
+ spinner.join();
+ resumer.join();
+ }
+
+ public static native int nativeSpinAndResume(Thread cur);
+ public static native void nativeResume();
+ public static native boolean isNativeThreadSpinning();
+ public static native void waitForNativeResumeFinished();
+ public static native void waitForNativeResumeStarted();
+}
diff --git a/test/Android.bp b/test/Android.bp
index 7d7afa5..f219b85 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -253,6 +253,7 @@
"ti-agent/breakpoint_helper.cc",
"ti-agent/common_helper.cc",
"ti-agent/redefinition_helper.cc",
+ "ti-agent/suspension_helper.cc",
"ti-agent/trace_helper.cc",
// This is the list of non-special OnLoad things and excludes BCI and anything that depends
// on ART internals.
@@ -287,6 +288,8 @@
"993-breakpoints/breakpoints.cc",
"996-breakpoint-obsolete/obsolete_breakpoints.cc",
"1901-get-bytecodes/bytecodes.cc",
+ "1905-suspend-native/native_suspend.cc",
+ "1908-suspend-native-resume-self/native_suspend_resume.cc",
],
shared_libs: [
"libbase",
diff --git a/test/ti-agent/suspension_helper.cc b/test/ti-agent/suspension_helper.cc
new file mode 100644
index 0000000..b685cb2
--- /dev/null
+++ b/test/ti-agent/suspension_helper.cc
@@ -0,0 +1,98 @@
+/*
+ * 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 "jni.h"
+#include "jvmti.h"
+
+#include <vector>
+
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace common_suspension {
+
+extern "C" JNIEXPORT jboolean JNICALL Java_art_Suspension_isSuspended(
+ JNIEnv* env, jclass, jthread thr) {
+ jint state;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetThreadState(thr, &state))) {
+ return false;
+ }
+ return (state & JVMTI_THREAD_STATE_SUSPENDED) != 0;
+}
+
+static std::vector<jthread> CopyToVector(JNIEnv* env, jobjectArray thrs) {
+ jsize len = env->GetArrayLength(thrs);
+ std::vector<jthread> ret;
+ for (jsize i = 0; i < len; i++) {
+ ret.push_back(reinterpret_cast<jthread>(env->GetObjectArrayElement(thrs, i)));
+ }
+ return ret;
+}
+
+extern "C" JNIEXPORT jintArray JNICALL Java_art_Suspension_resumeList(JNIEnv* env,
+ jclass,
+ jobjectArray thr) {
+ static_assert(sizeof(jvmtiError) == sizeof(jint), "cannot use jintArray as jvmtiError array");
+ std::vector<jthread> threads(CopyToVector(env, thr));
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+ jintArray ret = env->NewIntArray(threads.size());
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+ jint* elems = env->GetIntArrayElements(ret, nullptr);
+ JvmtiErrorToException(env, jvmti_env,
+ jvmti_env->ResumeThreadList(threads.size(),
+ threads.data(),
+ reinterpret_cast<jvmtiError*>(elems)));
+ env->ReleaseIntArrayElements(ret, elems, 0);
+ return ret;
+}
+
+extern "C" JNIEXPORT jintArray JNICALL Java_art_Suspension_suspendList(JNIEnv* env,
+ jclass,
+ jobjectArray thrs) {
+ static_assert(sizeof(jvmtiError) == sizeof(jint), "cannot use jintArray as jvmtiError array");
+ std::vector<jthread> threads(CopyToVector(env, thrs));
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+ jintArray ret = env->NewIntArray(threads.size());
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+ jint* elems = env->GetIntArrayElements(ret, nullptr);
+ JvmtiErrorToException(env, jvmti_env,
+ jvmti_env->SuspendThreadList(threads.size(),
+ threads.data(),
+ reinterpret_cast<jvmtiError*>(elems)));
+ env->ReleaseIntArrayElements(ret, elems, 0);
+ return ret;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Suspension_resume(JNIEnv* env, jclass, jthread thr) {
+ JvmtiErrorToException(env, jvmti_env, jvmti_env->ResumeThread(thr));
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Suspension_suspend(JNIEnv* env, jclass, jthread thr) {
+ JvmtiErrorToException(env, jvmti_env, jvmti_env->SuspendThread(thr));
+}
+
+} // namespace common_suspension
+} // namespace art
+