diff options
author | 2017-06-30 08:31:59 -0700 | |
---|---|---|
committer | 2017-07-13 09:11:03 -0700 | |
commit | 88fd720b6799184c8ad61e766a6d37af33ed30ef (patch) | |
tree | 80e49456eafd44b0ad8790be456ae06949fcc506 | |
parent | 1cba8d219331e4d6994359e8f9104e5db2c8f8a9 (diff) |
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
66 files changed, 1686 insertions, 35 deletions
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc index b0394a5255..a472b67fcd 100644 --- a/runtime/base/mutex.cc +++ b/runtime/base/mutex.cc @@ -68,6 +68,7 @@ ConditionVariable* Locks::thread_exit_cond_ = nullptr; 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 @@ void Locks::Init() { 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 @@ void Locks::Init() { } \ 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 e77d8d749d..7a472e741b 100644 --- a/runtime/base/mutex.h +++ b/runtime/base/mutex.h @@ -116,6 +116,7 @@ enum LockLevel { kTraceLock, kHeapBitmapLock, kMutatorLock, + kUserCodeSuspensionLock, kInstrumentEntrypointsLock, kZygoteCreationLock, @@ -578,6 +579,11 @@ class Locks { // 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 @@ class Locks { // 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 b59511214d..7fcc28c949 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -2480,7 +2480,8 @@ void Dbg::ResumeThread(JDWP::ObjectId thread_id) { 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 @@ class ScopedDebuggerThreadSuspension { ~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 @@ JDWP::JdwpError Dbg::PrepareInvokeMethod(uint32_t request_id, JDWP::ObjectId thr 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 3e3eaae13a..5c63dcad78 100644 --- a/runtime/monitor.cc +++ b/runtime/monitor.cc @@ -910,7 +910,8 @@ void Monitor::InflateThinLocked(Thread* self, Handle<mirror::Object> obj, LockWo // 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 7d2d0e5bb9..2aeef60d00 100644 --- a/runtime/native/dalvik_system_VMStack.cc +++ b/runtime/native/dalvik_system_VMStack.cc @@ -62,7 +62,8 @@ static jobject GetThreadStack(const ScopedFastNativeObjectAccess& soa, jobject p 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 8b76327fa8..4ce72edd7b 100644 --- a/runtime/native/java_lang_Thread.cc +++ b/runtime/native/java_lang_Thread.cc @@ -155,7 +155,8 @@ static void Thread_nativeSetName(JNIEnv* env, jobject peer, jstring java_name) { 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 c516b66d93..125d737958 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 @@ static jobjectArray DdmVmInternal_getStackTraceById(JNIEnv* env, jclass, jint th 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 4560dda4dd..6a8f2cedca 100644 --- a/runtime/openjdkjvm/OpenjdkJvm.cc +++ b/runtime/openjdkjvm/OpenjdkJvm.cc @@ -432,7 +432,8 @@ JNIEXPORT void JVM_SetNativeThreadName(JNIEnv* env, jobject jthread, jstring jav 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 505e844c92..4dff66072a 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -133,34 +133,34 @@ class JvmtiFunctions { 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 c63e50252b..4d5bb95f2c 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -233,7 +233,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .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 2cc2a26c3b..3d447dc7ac 100644 --- a/runtime/openjdkjvmti/ti_thread.cc +++ b/runtime/openjdkjvmti/ti_thread.cc @@ -433,6 +433,9 @@ jvmtiError ThreadUtil::GetThreadState(jvmtiEnv* env ATTRIBUTE_UNUSED, 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 @@ jvmtiError ThreadUtil::RunAgentThread(jvmtiEnv* jvmti_env, 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 939aea7a29..57967eb219 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 @@ class ThreadUtil { 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 27c4d3207b..289a1a4fb3 100644 --- a/runtime/suspend_reason.h +++ b/runtime/suspend_reason.h @@ -28,6 +28,8 @@ enum class SuspendReason { 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 95608b5f63..b5a962691b 100644 --- a/runtime/thread-inl.h +++ b/runtime/thread-inl.h @@ -121,10 +121,20 @@ inline bool Thread::IsThreadSuspensionAllowable() const { 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 @@ inline void Thread::AssertThreadSuspensionIsAllowable(bool check_locks) const { 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 @@ inline void Thread::AssertThreadSuspensionIsAllowable(bool check_locks) const { } } } + // 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 36ecd3398c..6a3f9e7056 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -1208,6 +1208,15 @@ bool Thread::ModifySuspendCountInternal(Thread* self, 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 @@ bool Thread::ModifySuspendCountInternal(Thread* self, 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 e785ddc803..0128c96d19 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -228,6 +228,11 @@ class Thread { 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 @@ class Thread { 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 @@ class Thread { // 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 fc767ed899..9c938ffe18 100644 --- a/runtime/thread_list.cc +++ b/runtime/thread_list.cc @@ -828,7 +828,7 @@ void ThreadList::ResumeAll() { } } -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 @@ void ThreadList::Resume(Thread* thread, SuspendReason reason) { 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; + } + if (UNLIKELY(!thread->ModifySuspendCount(self, -1, nullptr, reason))) { + LOG(ERROR) << "Resume(" << reinterpret_cast<void*>(thread) + << ") could not modify suspend count."; + return false; } - bool updated = thread->ModifySuspendCount(self, -1, nullptr, reason); - DCHECK(updated); } { @@ -860,6 +867,7 @@ void ThreadList::Resume(Thread* thread, SuspendReason reason) { } 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 41c5e328b0..11f272c48c 100644 --- a/runtime/thread_list.h +++ b/runtime/thread_list.h @@ -65,8 +65,8 @@ class ThreadList { 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 c6e62ae6cd..e92b873956 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 6940491e3c..9827e3f968 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 0000000000..e69de29bb2 --- /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 0000000000..c49a20f971 --- /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 0000000000..e92b873956 --- /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 0000000000..0bc7ba1a39 --- /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 0000000000..16e62ccac9 --- /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 0000000000..2bbfacf8ef --- /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 0000000000..e69de29bb2 --- /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 0000000000..779becc020 --- /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 0000000000..e92b873956 --- /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 0000000000..bd2028f323 --- /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 0000000000..16e62ccac9 --- /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 0000000000..cf2a55c44a --- /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 0000000000..321b8a34f1 --- /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 0000000000..5d2415b29d --- /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 0000000000..e92b873956 --- /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 0000000000..a0e71c6a45 --- /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 0000000000..16e62ccac9 --- /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 0000000000..8a52aa0e73 --- /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 0000000000..43b2669fab --- /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 0000000000..3545d5937c --- /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 0000000000..95b8da22ce --- /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 0000000000..e92b873956 --- /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 0000000000..42c02d0f42 --- /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 0000000000..16e62ccac9 --- /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 0000000000..ec3901935f --- /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 0000000000..503d728cb0 --- /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 0000000000..2b2f4e15ae --- /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 0000000000..e92b873956 --- /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 0000000000..1c8432c27a --- /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 0000000000..16e62ccac9 --- /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 0000000000..9bb272eded --- /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 0000000000..cd9b53f4a8 --- /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 0000000000..923c545421 --- /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 0000000000..e92b873956 --- /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 0000000000..910848ad1c --- /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 0000000000..16e62ccac9 --- /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 0000000000..504f7f3381 --- /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 0000000000..13cc517d68 --- /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 0000000000..3545d5937c --- /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 0000000000..158b22cce6 --- /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 0000000000..e92b873956 --- /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 0000000000..312adc44de --- /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 0000000000..16e62ccac9 --- /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 0000000000..9b7020adb8 --- /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 7d7afa5044..f219b854c3 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -253,6 +253,7 @@ art_cc_defaults { "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 @@ art_cc_defaults { "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 0000000000..b685cb2a55 --- /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 + |