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
+