Implement JVMTI can_signal_thread capability.

Implements the JVMTI can_signal_thread capability and all associated
methods and behaviors. This includes both the StopThread and
InterruptThread functions.

This CL contains the tests for the previous CL.

Test: ./test.py --host -j50
Test: stress --cpu 59 && while ./test/run-test --host 1934; do; done

Bug: 62821960
Bug: 34415266
Change-Id: I7b6fc37da0d2673caa993e486f078cf129d74c0f
diff --git a/test/1934-jvmti-signal-thread/expected.txt b/test/1934-jvmti-signal-thread/expected.txt
new file mode 100644
index 0000000..69a0e9e
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/expected.txt
@@ -0,0 +1,27 @@
+Interrupt before start
+interrupting other thread before starting
+Caught exception java.lang.RuntimeException: JVMTI_ERROR_THREAD_NOT_ALIVE
+Stop before start
+stopping other thread before starting
+Caught exception java.lang.RuntimeException: JVMTI_ERROR_THREAD_NOT_ALIVE
+Interrupt recur
+Interrupting other thread recurring
+Other thread Interrupted. err: java.lang.Error: Interrupted!
+Stop Recur
+stopping other thread recurring
+Other thread Stopped by: java.lang.Error: AWESOME!
+Interrupt spinning
+Interrupting other thread spinning
+Other thread Interrupted.
+Stop spinning
+stopping other thread spinning
+Other thread Stopped by: java.lang.Error: AWESOME!
+Interrupt wait
+interrupting other thread waiting
+Other thread interrupted. err: java.lang.Error: Interrupted!
+Stop wait
+stopping other thread waiting
+Other thread Stopped by: java.lang.Error: AWESOME
+Stop in native
+stopping other thread
+Other thread Stopped by: java.lang.Error: AWESOME
diff --git a/test/1934-jvmti-signal-thread/info.txt b/test/1934-jvmti-signal-thread/info.txt
new file mode 100644
index 0000000..c8c9189
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that the GetBytecodes function works as expected.
diff --git a/test/1934-jvmti-signal-thread/run b/test/1934-jvmti-signal-thread/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-run "$@" --jvmti
diff --git a/test/1934-jvmti-signal-thread/signal_threads.cc b/test/1934-jvmti-signal-thread/signal_threads.cc
new file mode 100644
index 0000000..726a7a86
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/signal_threads.cc
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <pthread.h>
+
+#include <cstdio>
+#include <iostream>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "jni.h"
+#include "jvmti.h"
+
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1934SignalThreads {
+
+struct NativeMonitor {
+  jrawMonitorID continue_monitor;
+  bool should_continue;
+  jrawMonitorID start_monitor;
+  bool should_start;
+};
+
+extern "C" JNIEXPORT jlong JNICALL Java_art_Test1934_allocNativeMonitor(JNIEnv* env, jclass) {
+  NativeMonitor* mon;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->Allocate(sizeof(NativeMonitor),
+                                                reinterpret_cast<unsigned char**>(&mon)))) {
+    return -1l;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->CreateRawMonitor("test-1934 start",
+                                                        &mon->start_monitor))) {
+    return -1l;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->CreateRawMonitor("test-1934 continue",
+                                                        &mon->continue_monitor))) {
+    return -1l;
+  }
+  mon->should_continue = false;
+  mon->should_start = false;
+  return static_cast<jlong>(reinterpret_cast<intptr_t>(mon));
+}
+
+extern "C" JNIEXPORT void Java_art_Test1934_nativeWaitForOtherThread(JNIEnv* env,
+                                                                     jclass,
+                                                                     jlong id) {
+  NativeMonitor* mon = reinterpret_cast<NativeMonitor*>(static_cast<intptr_t>(id));
+  // Start
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorEnter(mon->start_monitor))) {
+    return;
+  }
+  mon->should_start = true;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->RawMonitorNotifyAll(mon->start_monitor))) {
+    JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->start_monitor));
+    return;
+  }
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->start_monitor))) {
+    return;
+  }
+
+  // Finish
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorEnter(mon->continue_monitor))) {
+    return;
+  }
+  while (!mon->should_continue) {
+    if (JvmtiErrorToException(env,
+                              jvmti_env,
+                              jvmti_env->RawMonitorWait(mon->continue_monitor, -1l))) {
+      JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->continue_monitor));
+      return;
+    }
+  }
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->continue_monitor));
+}
+
+extern "C" JNIEXPORT void Java_art_Test1934_nativeDoInterleaved(JNIEnv* env,
+                                                                jclass,
+                                                                jlong id,
+                                                                jobject closure) {
+  NativeMonitor* mon = reinterpret_cast<NativeMonitor*>(static_cast<intptr_t>(id));
+  // Wait for start.
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorEnter(mon->start_monitor))) {
+    return;
+  }
+  while (!mon->should_start) {
+    if (JvmtiErrorToException(env,
+                              jvmti_env,
+                              jvmti_env->RawMonitorWait(mon->start_monitor, -1l))) {
+      return;
+    }
+  }
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->start_monitor))) {
+    return;
+  }
+
+  // Call closure.
+  ScopedLocalRef<jclass> runnable_klass(env, env->FindClass("java/lang/Runnable"));
+  if (env->ExceptionCheck()) {
+    return;
+  }
+  jmethodID doRun = env->GetMethodID(runnable_klass.get(), "run", "()V");
+  if (env->ExceptionCheck()) {
+    return;
+  }
+  env->CallVoidMethod(closure, doRun);
+
+  // Tell other thread to finish.
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorEnter(mon->continue_monitor))) {
+    return;
+  }
+  mon->should_continue = true;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->RawMonitorNotifyAll(mon->continue_monitor))) {
+    JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->continue_monitor));
+    return;
+  }
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(mon->continue_monitor));
+}
+
+extern "C" JNIEXPORT void Java_art_Test1934_destroyNativeMonitor(JNIEnv*, jclass, jlong id) {
+  NativeMonitor* mon = reinterpret_cast<NativeMonitor*>(static_cast<intptr_t>(id));
+  jvmti_env->DestroyRawMonitor(mon->start_monitor);
+  jvmti_env->DestroyRawMonitor(mon->continue_monitor);
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(mon));
+}
+
+}  // namespace Test1934SignalThreads
+}  // namespace art
+
diff --git a/test/1934-jvmti-signal-thread/src/Main.java b/test/1934-jvmti-signal-thread/src/Main.java
new file mode 100644
index 0000000..539763c
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test1934.run();
+  }
+}
diff --git a/test/1934-jvmti-signal-thread/src/art/Monitors.java b/test/1934-jvmti-signal-thread/src/art/Monitors.java
new file mode 100644
index 0000000..7fe2b60
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/src/art/Monitors.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.*;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class Monitors {
+  public native static void setupMonitorEvents(
+      Class<?> method_klass,
+      Method monitor_contended_enter_event,
+      Method monitor_contended_entered_event,
+      Method monitor_wait_event,
+      Method monitor_waited_event,
+      Class<?> lock_klass,
+      Thread thr);
+  public native static void stopMonitorEvents();
+
+  public static class NamedLock {
+    public final String name;
+    private volatile int calledNotify;
+    public NamedLock(String name) {
+      this.name = name;
+      calledNotify = 0;
+    }
+
+    public String toString() {
+      return String.format("NamedLock[%s]", name);
+    }
+
+    public final void DoWait() throws Exception {
+      final int v = calledNotify;
+      while (v == calledNotify) {
+        wait();
+      }
+    }
+
+    public final void DoWait(long t) throws Exception {
+      final int v = calledNotify;
+      final long target = System.currentTimeMillis() + (t / 2);
+      while (v == calledNotify && (t < 0 || System.currentTimeMillis() < target)) {
+        wait(t);
+      }
+    }
+
+    public final void DoNotifyAll() throws Exception {
+      calledNotify++;
+      notifyAll();
+    }
+
+    public final void DoNotify() throws Exception {
+      calledNotify++;
+      notify();
+    }
+  }
+
+  public static final class MonitorUsage {
+    public final Object monitor;
+    public final Thread owner;
+    public final int entryCount;
+    public final Thread[] waiters;
+    public final Thread[] notifyWaiters;
+
+    public MonitorUsage(
+        Object monitor,
+        Thread owner,
+        int entryCount,
+        Thread[] waiters,
+        Thread[] notifyWaiters) {
+      this.monitor = monitor;
+      this.entryCount = entryCount;
+      this.owner = owner;
+      this.waiters = waiters;
+      this.notifyWaiters = notifyWaiters;
+    }
+
+    private static String toNameList(Thread[] ts) {
+      return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray());
+    }
+
+    public String toString() {
+      return String.format(
+          "MonitorUsage{ monitor: %s, owner: %s, entryCount: %d, waiters: %s, notify_waiters: %s }",
+          monitor,
+          (owner != null) ? owner.getName() : "<NULL>",
+          entryCount,
+          toNameList(waiters),
+          toNameList(notifyWaiters));
+    }
+  }
+
+  public static native MonitorUsage getObjectMonitorUsage(Object monitor);
+  public static native Object getCurrentContendedMonitor(Thread thr);
+
+  public static class TestException extends Error {
+    public TestException() { super(); }
+    public TestException(String s) { super(s); }
+    public TestException(String s, Throwable c) { super(s, c); }
+  }
+
+  public static class LockController {
+    private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT }
+
+    public final NamedLock lock;
+    public final long timeout;
+    private final AtomicStampedReference<Action> action;
+    private volatile Thread runner = null;
+    private volatile boolean started = false;
+    private volatile boolean held = false;
+    private static final AtomicInteger cnt = new AtomicInteger(0);
+    private volatile Throwable exe;
+
+    public LockController(NamedLock lock) {
+      this(lock, 10 * 1000);
+    }
+    public LockController(NamedLock lock, long timeout) {
+      this.lock = lock;
+      this.timeout = timeout;
+      this.action = new AtomicStampedReference(Action.HOLD, 0);
+      this.exe = null;
+    }
+
+    public boolean IsWorkerThread(Thread thd) {
+      return Objects.equals(runner, thd);
+    }
+
+    public boolean IsLocked() {
+      checkException();
+      return held;
+    }
+
+    public void checkException() {
+      if (exe != null) {
+        throw new TestException("Exception thrown by other thread!", exe);
+      }
+    }
+
+    private void setAction(Action a) {
+      int stamp = action.getStamp();
+      // Wait for it to be HOLD before updating.
+      while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) {
+        stamp = action.getStamp();
+      }
+    }
+
+    public synchronized void suspendWorker() throws Exception {
+      checkException();
+      if (runner == null) {
+        throw new TestException("We don't have any runner holding  " + lock);
+      }
+      Suspension.suspend(runner);
+    }
+
+    public Object getWorkerContendedMonitor() throws Exception {
+      checkException();
+      if (runner == null) {
+        return null;
+      }
+      return getCurrentContendedMonitor(runner);
+    }
+
+    public synchronized void DoLock() {
+      if (IsLocked()) {
+        throw new Error("lock is already acquired or being acquired.");
+      }
+      if (runner != null) {
+        throw new Error("Already have thread!");
+      }
+      runner = new Thread(() -> {
+        started = true;
+        try {
+          synchronized (lock) {
+            held = true;
+            int[] stamp_h = new int[] { -1 };
+            Action cur_action = Action.HOLD;
+            try {
+              while (true) {
+                cur_action = action.get(stamp_h);
+                int stamp = stamp_h[0];
+                if (cur_action == Action.RELEASE) {
+                  // The other thread will deal with reseting action.
+                  break;
+                }
+                try {
+                  switch (cur_action) {
+                    case HOLD:
+                      Thread.yield();
+                      break;
+                    case NOTIFY:
+                      lock.DoNotify();
+                      break;
+                    case NOTIFY_ALL:
+                      lock.DoNotifyAll();
+                      break;
+                    case TIMED_WAIT:
+                      lock.DoWait(timeout);
+                      break;
+                    case WAIT:
+                      lock.DoWait();
+                      break;
+                    default:
+                      throw new Error("Unknown action " + action);
+                  }
+                } finally {
+                  // reset action back to hold if it isn't something else.
+                  action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1);
+                }
+              }
+            } catch (Exception e) {
+              throw new TestException("Got an error while performing action " + cur_action, e);
+            }
+          }
+        } finally {
+          held = false;
+          started = false;
+        }
+      }, "Locker thread " + cnt.getAndIncrement() + " for " + lock);
+      // Make sure we can get any exceptions this throws.
+      runner.setUncaughtExceptionHandler((t, e) -> { exe = e; });
+      runner.start();
+    }
+
+    public void waitForLockToBeHeld() throws Exception {
+      while (true) {
+        if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) {
+          return;
+        }
+      }
+    }
+
+    public synchronized void waitForNotifySleep() throws Exception {
+      if (runner == null) {
+        throw new Error("No thread trying to lock!");
+      }
+      do {
+        checkException();
+      } while (!started ||
+          !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner));
+    }
+
+    public synchronized void waitForContendedSleep() throws Exception {
+      if (runner == null) {
+        throw new Error("No thread trying to lock!");
+      }
+      do {
+        checkException();
+      } while (!started ||
+          runner.getState() != Thread.State.BLOCKED ||
+          !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner));
+    }
+
+    public synchronized void DoNotify() {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.NOTIFY);
+    }
+
+    public synchronized void DoNotifyAll() {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.NOTIFY_ALL);
+    }
+
+    public synchronized void DoTimedWait() throws Exception {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.TIMED_WAIT);
+    }
+
+    public synchronized void DoWait() throws Exception {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.WAIT);
+    }
+
+    public synchronized void interruptWorker() throws Exception {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      runner.interrupt();
+    }
+
+    public synchronized void waitForActionToFinish() throws Exception {
+      checkException();
+      while (action.getReference() != Action.HOLD) { checkException(); }
+    }
+
+    public synchronized void DoUnlock() throws Exception {
+      Error throwing = null;
+      if (!IsLocked()) {
+        // We might just be racing some exception that was thrown by the worker thread. Cache the
+        // exception, we will throw one from the worker before this one.
+        throwing = new Error("Not locked!");
+      }
+      setAction(Action.RELEASE);
+      Thread run = runner;
+      runner = null;
+      while (held) {}
+      run.join();
+      action.set(Action.HOLD, 0);
+      // Make sure to throw any exception that occurred since it might not have unlocked due to our
+      // request.
+      checkException();
+      DoCleanup();
+      if (throwing != null) {
+        throw throwing;
+      }
+    }
+
+    public synchronized void DoCleanup() throws Exception {
+      if (runner != null) {
+        Thread run = runner;
+        runner = null;
+        while (held) {}
+        run.join();
+      }
+      action.set(Action.HOLD, 0);
+      exe = null;
+    }
+  }
+}
+
diff --git a/test/1934-jvmti-signal-thread/src/art/Suspension.java b/test/1934-jvmti-signal-thread/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1934-jvmti-signal-thread/src/art/Test1934.java b/test/1934-jvmti-signal-thread/src/art/Test1934.java
new file mode 100644
index 0000000..552570a
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/src/art/Test1934.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.util.concurrent.Semaphore;
+import java.util.Objects;
+
+public class Test1934 {
+  public static final boolean PRINT_STACK_TRACE = false;
+
+  public static void run() throws Exception {
+    System.out.println("Interrupt before start");
+    testInterruptBeforeStart();
+
+    System.out.println("Stop before start");
+    testStopBeforeStart();
+
+    System.out.println("Interrupt recur");
+    testInterruptRecur();
+
+    System.out.println("Stop Recur");
+    testStopRecur();
+
+    System.out.println("Interrupt spinning");
+    testInterruptSpinning();
+
+    System.out.println("Stop spinning");
+    testStopSpinning();
+
+    System.out.println("Interrupt wait");
+    testInterruptWait();
+
+    System.out.println("Stop wait");
+    testStopWait();
+
+    System.out.println("Stop in native");
+    testStopInNative();
+  }
+
+  public static void testStopBeforeStart() throws Exception {
+    final Throwable[] out_err = new Throwable[] { null, };
+    final Object tst = new Object();
+    Thread target = new Thread(() -> { while (true) { } }, "waiting thread!");
+    target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+    System.out.println("stopping other thread before starting");
+    try {
+      Threads.stopThread(target, new Error("AWESOME"));
+      target.start();
+      target.join();
+      System.out.println("Other thread Stopped by: " + out_err[0]);
+      if (PRINT_STACK_TRACE && out_err[0] != null) {
+        out_err[0].printStackTrace();
+      }
+    } catch (Exception e) {
+      System.out.println("Caught exception " + e);
+    }
+  }
+
+  public static void testInterruptBeforeStart() throws Exception {
+    final Throwable[] out_err = new Throwable[] { null, };
+    final Object tst = new Object();
+    Thread target = new Thread(() -> { while (true) { } }, "waiting thread!");
+    target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+    System.out.println("interrupting other thread before starting");
+    try {
+      Threads.interruptThread(target);
+      target.start();
+      target.join();
+      System.out.println("Other thread interrupted. err: " + out_err[0]);
+      if (PRINT_STACK_TRACE && out_err[0] != null) {
+        out_err[0].printStackTrace();
+      }
+    } catch (Exception e) {
+      System.out.println("Caught exception " + e);
+    }
+  }
+
+  public static void testStopWait() throws Exception {
+    final Throwable[] out_err = new Throwable[] { null, };
+    final Object tst = new Object();
+    final Semaphore sem = new Semaphore(0);
+    Thread target = new Thread(() -> {
+      sem.release();
+      while (true) {
+        try {
+          synchronized (tst) {
+            tst.wait();
+          }
+        } catch (InterruptedException e) { throw new Error("Interrupted!", e); }
+      }
+    }, "waiting thread!");
+    target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+    target.start();
+    sem.acquire();
+    while (!Objects.equals(Monitors.getCurrentContendedMonitor(target), tst)) {}
+    System.out.println("stopping other thread waiting");
+    Threads.stopThread(target, new Error("AWESOME"));
+    target.join();
+    System.out.println("Other thread Stopped by: " + out_err[0]);
+    if (PRINT_STACK_TRACE && out_err[0] != null) {
+      out_err[0].printStackTrace();
+    }
+  }
+
+  public static void testInterruptWait() throws Exception {
+    final Throwable[] out_err = new Throwable[] { null, };
+    final Object tst = new Object();
+    final Semaphore sem = new Semaphore(0);
+    Thread target = new Thread(() -> {
+      sem.release();
+      while (true) {
+        try {
+          synchronized (tst) {
+            tst.wait();
+          }
+        } catch (InterruptedException e) { throw new Error("Interrupted!", e); }
+      }
+    }, "waiting thread!");
+    target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+    target.start();
+    sem.acquire();
+    while (!Objects.equals(Monitors.getCurrentContendedMonitor(target), tst)) {}
+    System.out.println("interrupting other thread waiting");
+    Threads.interruptThread(target);
+    target.join();
+    System.out.println("Other thread interrupted. err: " + out_err[0]);
+    if (PRINT_STACK_TRACE && out_err[0] != null) {
+      out_err[0].printStackTrace();
+    }
+  }
+
+  public static void doNothing() {}
+  public static native long allocNativeMonitor();
+  public static native void nativeWaitForOtherThread(long id);
+  public static native void nativeDoInterleaved(long id, Runnable op);
+  public static native void destroyNativeMonitor(long id);
+  public static void testStopInNative() throws Exception {
+    final Throwable[] out_err = new Throwable[] { null, };
+    final long native_monitor_id = allocNativeMonitor();
+    final Semaphore sem = new Semaphore(0);
+    Thread target = new Thread(() -> {
+      sem.release();
+      nativeWaitForOtherThread(native_monitor_id);
+      // We need to make sure we do something that can get the exception to be actually noticed.
+      doNothing();
+    }, "native waiting thread!");
+    target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+    target.start();
+    sem.acquire();
+    System.out.println("stopping other thread");
+    nativeDoInterleaved(
+        native_monitor_id,
+        () -> { Threads.stopThread(target, new Error("AWESOME")); });
+    target.join();
+    System.out.println("Other thread Stopped by: " + out_err[0]);
+    if (PRINT_STACK_TRACE && out_err[0] != null) {
+      out_err[0].printStackTrace();
+    }
+    destroyNativeMonitor(native_monitor_id);
+  }
+
+  public static void doRecur(Runnable r) {
+    if (r != null) {
+      r.run();
+    }
+    doRecur(r);
+  }
+
+  public static void testStopRecur() throws Exception {
+    final Throwable[] out_err = new Throwable[] { null, };
+    final Semaphore sem = new Semaphore(0);
+    Thread target = new Thread(() -> {
+      sem.release();
+      while (true) {
+        try {
+          doRecur(null);
+        } catch (StackOverflowError e) {}
+      }
+    }, "recuring thread!");
+    target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+    target.start();
+    sem.acquire();
+    System.out.println("stopping other thread recurring");
+    Threads.stopThread(target, new Error("AWESOME!"));
+    target.join();
+    System.out.println("Other thread Stopped by: " + out_err[0]);
+    if (PRINT_STACK_TRACE && out_err[0] != null) {
+      out_err[0].printStackTrace();
+    }
+  }
+
+  public static void testInterruptRecur() throws Exception {
+    final Throwable[] out_err = new Throwable[] { null, };
+    final Semaphore sem = new Semaphore(0);
+    Thread target = new Thread(() -> {
+      sem.release();
+      while (true) {
+        try {
+          doRecur(() -> {
+            if (Thread.currentThread().isInterrupted()) { throw new Error("Interrupted!"); }
+          });
+        } catch (StackOverflowError e) { }
+      }
+    }, "recuring thread!");
+    target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+    target.start();
+    sem.acquire();
+    System.out.println("Interrupting other thread recurring");
+    Threads.interruptThread(target);
+    target.join();
+    System.out.println("Other thread Interrupted. err: " + out_err[0]);
+    if (PRINT_STACK_TRACE && out_err[0] != null) {
+      out_err[0].printStackTrace();
+    }
+  }
+
+  public static void testStopSpinning() throws Exception {
+    final Throwable[] out_err = new Throwable[] { null, };
+    final Semaphore sem = new Semaphore(0);
+    Thread target = new Thread(() -> { sem.release(); while (true) {} }, "Spinning thread!");
+    target.setUncaughtExceptionHandler((t, e) -> { out_err[0] = e; });
+    target.start();
+    sem.acquire();
+    System.out.println("stopping other thread spinning");
+    Threads.stopThread(target, new Error("AWESOME!"));
+    target.join();
+    System.out.println("Other thread Stopped by: " + out_err[0]);
+    if (PRINT_STACK_TRACE && out_err[0] != null) {
+      out_err[0].printStackTrace();
+    }
+  }
+
+  public static void testInterruptSpinning() throws Exception {
+    final Semaphore sem = new Semaphore(0);
+    Thread target = new Thread(() -> {
+      sem.release();
+      while (!Thread.currentThread().isInterrupted()) { }
+    }, "Spinning thread!");
+    target.start();
+    sem.acquire();
+    System.out.println("Interrupting other thread spinning");
+    Threads.interruptThread(target);
+    target.join();
+    System.out.println("Other thread Interrupted.");
+  }
+}
diff --git a/test/1934-jvmti-signal-thread/src/art/Threads.java b/test/1934-jvmti-signal-thread/src/art/Threads.java
new file mode 100644
index 0000000..266813b
--- /dev/null
+++ b/test/1934-jvmti-signal-thread/src/art/Threads.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Threads {
+  public static native void interruptThread(Thread t);
+  public static native void stopThread(Thread t, Throwable thr);
+}