ART: Add raw monitor API

Add support for CreateRawMonitor, DestroyRawMonitor,
RawMonitorEnter, RawMonitorExit, RawMonitorWait, RawMonitorNotify
and RawMonitorNotifyAll.

Bug: 31455788
Test: m test-art-host-run-test-923-monitors
Change-Id: I4eeab8011b547ae0ea8b2317701c68ce56677f79
diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp
index be06dd7..b757b21 100644
--- a/runtime/openjdkjvmti/Android.bp
+++ b/runtime/openjdkjvmti/Android.bp
@@ -24,6 +24,7 @@
            "ti_field.cc",
            "ti_heap.cc",
            "ti_method.cc",
+           "ti_monitor.cc",
            "ti_object.cc",
            "ti_properties.cc",
            "ti_stack.cc",
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 936049f..c52dd76 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -50,6 +50,7 @@
 #include "ti_field.h"
 #include "ti_heap.h"
 #include "ti_method.h"
+#include "ti_monitor.h"
 #include "ti_object.h"
 #include "ti_properties.h"
 #include "ti_redefine.h"
@@ -748,31 +749,31 @@
   }
 
   static jvmtiError CreateRawMonitor(jvmtiEnv* env, const char* name, jrawMonitorID* monitor_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return MonitorUtil::CreateRawMonitor(env, name, monitor_ptr);
   }
 
   static jvmtiError DestroyRawMonitor(jvmtiEnv* env, jrawMonitorID monitor) {
-    return ERR(NOT_IMPLEMENTED);
+    return MonitorUtil::DestroyRawMonitor(env, monitor);
   }
 
   static jvmtiError RawMonitorEnter(jvmtiEnv* env, jrawMonitorID monitor) {
-    return ERR(NOT_IMPLEMENTED);
+    return MonitorUtil::RawMonitorEnter(env, monitor);
   }
 
   static jvmtiError RawMonitorExit(jvmtiEnv* env, jrawMonitorID monitor) {
-    return ERR(NOT_IMPLEMENTED);
+    return MonitorUtil::RawMonitorExit(env, monitor);
   }
 
   static jvmtiError RawMonitorWait(jvmtiEnv* env, jrawMonitorID monitor, jlong millis) {
-    return ERR(NOT_IMPLEMENTED);
+    return MonitorUtil::RawMonitorWait(env, monitor, millis);
   }
 
   static jvmtiError RawMonitorNotify(jvmtiEnv* env, jrawMonitorID monitor) {
-    return ERR(NOT_IMPLEMENTED);
+    return MonitorUtil::RawMonitorNotify(env, monitor);
   }
 
   static jvmtiError RawMonitorNotifyAll(jvmtiEnv* env, jrawMonitorID monitor) {
-    return ERR(NOT_IMPLEMENTED);
+    return MonitorUtil::RawMonitorNotifyAll(env, monitor);
   }
 
   static jvmtiError SetJNIFunctionTable(jvmtiEnv* env, const jniNativeInterface* function_table) {
diff --git a/runtime/openjdkjvmti/ti_monitor.cc b/runtime/openjdkjvmti/ti_monitor.cc
new file mode 100644
index 0000000..b827683
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_monitor.cc
@@ -0,0 +1,302 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_monitor.h"
+
+#include <atomic>
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+
+#include "art_jvmti.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-inl.h"
+
+namespace openjdkjvmti {
+
+// We cannot use ART monitors, as they require the mutator lock for contention locking. We
+// also cannot use pthread mutexes and condition variables (or C++11 abstractions) directly,
+// as the do not have the right semantics for recursive mutexes and waiting (wait only unlocks
+// the mutex once).
+// So go ahead and use a wrapper that does the counting explicitly.
+
+class JvmtiMonitor {
+ public:
+  JvmtiMonitor() : owner_(nullptr), count_(0) {
+  }
+
+  static bool Destroy(art::Thread* self, JvmtiMonitor* monitor) {
+    // Check whether this thread holds the monitor, or nobody does.
+    art::Thread* owner_thread = monitor->owner_.load(std::memory_order_relaxed);
+    if (owner_thread != nullptr && self != owner_thread) {
+      return false;
+    }
+
+    if (monitor->count_ > 0) {
+      monitor->count_ = 0;
+      monitor->owner_.store(nullptr, std::memory_order_relaxed);
+      monitor->mutex_.unlock();
+    }
+
+    delete monitor;
+    return true;
+  }
+
+  void MonitorEnter(art::Thread* self) {
+    // Check for recursive enter.
+    if (IsOwner(self)) {
+      count_++;
+      return;
+    }
+
+    mutex_.lock();
+
+    DCHECK(owner_.load(std::memory_order_relaxed) == nullptr);
+    owner_.store(self, std::memory_order_relaxed);
+    DCHECK_EQ(0u, count_);
+    count_ = 1;
+  }
+
+  bool MonitorExit(art::Thread* self) {
+    if (!IsOwner(self)) {
+      return false;
+    }
+
+    --count_;
+    if (count_ == 0u) {
+      owner_.store(nullptr, std::memory_order_relaxed);
+      mutex_.unlock();
+    }
+
+    return true;
+  }
+
+  bool Wait(art::Thread* self) {
+    auto wait_without_timeout = [&](std::unique_lock<std::mutex>& lk) {
+      cond_.wait(lk);
+    };
+    return Wait(self, wait_without_timeout);
+  }
+
+  bool Wait(art::Thread* self, uint64_t timeout_in_ms) {
+    auto wait_with_timeout = [&](std::unique_lock<std::mutex>& lk) {
+      cond_.wait_for(lk, std::chrono::milliseconds(timeout_in_ms));
+    };
+    return Wait(self, wait_with_timeout);
+  }
+
+  bool Notify(art::Thread* self) {
+    return Notify(self, [&]() { cond_.notify_one(); });
+  }
+
+  bool NotifyAll(art::Thread* self) {
+    return Notify(self, [&]() { cond_.notify_all(); });
+  }
+
+ private:
+  bool IsOwner(art::Thread* self) {
+    // There's a subtle correctness argument here for a relaxed load outside the critical section.
+    // A thread is guaranteed to see either its own latest store or another thread's store. If a
+    // thread sees another thread's store than it cannot be holding the lock.
+    art::Thread* owner_thread = owner_.load(std::memory_order_relaxed);
+    return self == owner_thread;
+  }
+
+  template <typename T>
+  bool Wait(art::Thread* self, T how_to_wait) {
+    if (!IsOwner(self)) {
+      return false;
+    }
+
+    size_t old_count = count_;
+
+    count_ = 0;
+    owner_.store(nullptr, std::memory_order_relaxed);
+
+    {
+      std::unique_lock<std::mutex> lk(mutex_, std::adopt_lock);
+      how_to_wait(lk);
+      lk.release();  // Do not unlock the mutex.
+    }
+
+    DCHECK(owner_.load(std::memory_order_relaxed) == nullptr);
+    owner_.store(self, std::memory_order_relaxed);
+    DCHECK_EQ(0u, count_);
+    count_ = old_count;
+
+    return true;
+  }
+
+  template <typename T>
+  bool Notify(art::Thread* self, T how_to_notify) {
+    if (!IsOwner(self)) {
+      return false;
+    }
+
+    how_to_notify();
+
+    return true;
+  }
+
+  std::mutex mutex_;
+  std::condition_variable cond_;
+  std::atomic<art::Thread*> owner_;
+  size_t count_;
+};
+
+static jrawMonitorID EncodeMonitor(JvmtiMonitor* monitor) {
+  return reinterpret_cast<jrawMonitorID>(monitor);
+}
+
+static JvmtiMonitor* DecodeMonitor(jrawMonitorID id) {
+  return reinterpret_cast<JvmtiMonitor*>(id);
+}
+
+jvmtiError MonitorUtil::CreateRawMonitor(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                         const char* name,
+                                         jrawMonitorID* monitor_ptr) {
+  if (name == nullptr || monitor_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  JvmtiMonitor* monitor = new JvmtiMonitor();
+  *monitor_ptr = EncodeMonitor(monitor);
+
+  return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::DestroyRawMonitor(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+  if (id == nullptr) {
+    return ERR(INVALID_MONITOR);
+  }
+
+  JvmtiMonitor* monitor = DecodeMonitor(id);
+  art::Thread* self = art::Thread::Current();
+
+  if (!JvmtiMonitor::Destroy(self, monitor)) {
+    return ERR(NOT_MONITOR_OWNER);
+  }
+
+  return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorEnter(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+  if (id == nullptr) {
+    return ERR(INVALID_MONITOR);
+  }
+
+  JvmtiMonitor* monitor = DecodeMonitor(id);
+  art::Thread* self = art::Thread::Current();
+
+  monitor->MonitorEnter(self);
+
+  return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorExit(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+  if (id == nullptr) {
+    return ERR(INVALID_MONITOR);
+  }
+
+  JvmtiMonitor* monitor = DecodeMonitor(id);
+  art::Thread* self = art::Thread::Current();
+
+  if (!monitor->MonitorExit(self)) {
+    return ERR(NOT_MONITOR_OWNER);
+  }
+
+  return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorWait(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                       jrawMonitorID id,
+                                       jlong millis) {
+  if (id == nullptr) {
+    return ERR(INVALID_MONITOR);
+  }
+
+  JvmtiMonitor* monitor = DecodeMonitor(id);
+  art::Thread* self = art::Thread::Current();
+
+  // This is not in the spec, but it's the only thing that makes sense (and agrees with
+  // Object.wait).
+  if (millis < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+
+  bool result = (millis > 0)
+      ? monitor->Wait(self, static_cast<uint64_t>(millis))
+      : monitor->Wait(self);
+
+  if (!result) {
+    return ERR(NOT_MONITOR_OWNER);
+  }
+
+  // TODO: Make sure that is really what we should be checking here.
+  if (self->IsInterrupted()) {
+    return ERR(INTERRUPT);
+  }
+
+  return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorNotify(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+  if (id == nullptr) {
+    return ERR(INVALID_MONITOR);
+  }
+
+  JvmtiMonitor* monitor = DecodeMonitor(id);
+  art::Thread* self = art::Thread::Current();
+
+  if (!monitor->Notify(self)) {
+    return ERR(NOT_MONITOR_OWNER);
+  }
+
+  return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorNotifyAll(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+  if (id == nullptr) {
+    return ERR(INVALID_MONITOR);
+  }
+
+  JvmtiMonitor* monitor = DecodeMonitor(id);
+  art::Thread* self = art::Thread::Current();
+
+  if (!monitor->NotifyAll(self)) {
+    return ERR(NOT_MONITOR_OWNER);
+  }
+
+  return ERR(NONE);
+}
+
+}  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_monitor.h b/runtime/openjdkjvmti/ti_monitor.h
new file mode 100644
index 0000000..96ccb0d
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_monitor.h
@@ -0,0 +1,59 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_MONITOR_H_
+#define ART_RUNTIME_OPENJDKJVMTI_TI_MONITOR_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class MonitorUtil {
+ public:
+  static jvmtiError CreateRawMonitor(jvmtiEnv* env, const char* name, jrawMonitorID* monitor_ptr);
+
+  static jvmtiError DestroyRawMonitor(jvmtiEnv* env, jrawMonitorID monitor);
+
+  static jvmtiError RawMonitorEnter(jvmtiEnv* env, jrawMonitorID monitor);
+
+  static jvmtiError RawMonitorExit(jvmtiEnv* env, jrawMonitorID monitor);
+
+  static jvmtiError RawMonitorWait(jvmtiEnv* env, jrawMonitorID monitor, jlong millis);
+
+  static jvmtiError RawMonitorNotify(jvmtiEnv* env, jrawMonitorID monitor);
+
+  static jvmtiError RawMonitorNotifyAll(jvmtiEnv* env, jrawMonitorID monitor);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_MONITOR_H_
diff --git a/test/923-monitors/build b/test/923-monitors/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/923-monitors/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 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-build "$@" --experimental agents
diff --git a/test/923-monitors/expected.txt b/test/923-monitors/expected.txt
new file mode 100644
index 0000000..5fbfb98
--- /dev/null
+++ b/test/923-monitors/expected.txt
@@ -0,0 +1,38 @@
+Unlock
+JVMTI_ERROR_NOT_MONITOR_OWNER
+Lock
+Unlock
+Unlock
+JVMTI_ERROR_NOT_MONITOR_OWNER
+Lock
+Lock
+Unlock
+Unlock
+Unlock
+JVMTI_ERROR_NOT_MONITOR_OWNER
+Wait
+JVMTI_ERROR_NOT_MONITOR_OWNER
+Wait
+JVMTI_ERROR_ILLEGAL_ARGUMENT
+Wait
+JVMTI_ERROR_NOT_MONITOR_OWNER
+Lock
+Wait
+Unlock
+Unlock
+JVMTI_ERROR_NOT_MONITOR_OWNER
+Notify
+JVMTI_ERROR_NOT_MONITOR_OWNER
+Lock
+Notify
+Unlock
+Unlock
+JVMTI_ERROR_NOT_MONITOR_OWNER
+NotifyAll
+JVMTI_ERROR_NOT_MONITOR_OWNER
+Lock
+NotifyAll
+Unlock
+Unlock
+JVMTI_ERROR_NOT_MONITOR_OWNER
+Done
diff --git a/test/923-monitors/info.txt b/test/923-monitors/info.txt
new file mode 100644
index 0000000..875a5f6
--- /dev/null
+++ b/test/923-monitors/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/923-monitors/monitors.cc b/test/923-monitors/monitors.cc
new file mode 100644
index 0000000..2aa36cb
--- /dev/null
+++ b/test/923-monitors/monitors.cc
@@ -0,0 +1,100 @@
+/*
+ * 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 "monitors.h"
+
+#include <stdio.h>
+
+#include "base/macros.h"
+#include "jni.h"
+#include "openjdkjvmti/jvmti.h"
+#include "ScopedUtfChars.h"
+
+#include "ti-agent/common_helper.h"
+#include "ti-agent/common_load.h"
+
+namespace art {
+namespace Test923Monitors {
+
+
+static jlong MonitorToLong(jrawMonitorID id) {
+  return static_cast<jlong>(reinterpret_cast<uintptr_t>(id));
+}
+
+static jrawMonitorID LongToMonitor(jlong l) {
+  return reinterpret_cast<jrawMonitorID>(static_cast<uintptr_t>(l));
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_Main_createRawMonitor(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) {
+  jrawMonitorID id;
+  jvmtiError result = jvmti_env->CreateRawMonitor("dummy", &id);
+  if (JvmtiErrorToException(env, result)) {
+    return 0;
+  }
+  return MonitorToLong(id);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_destroyRawMonitor(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jlong l) {
+  jvmtiError result = jvmti_env->DestroyRawMonitor(LongToMonitor(l));
+  JvmtiErrorToException(env, result);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_rawMonitorEnter(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jlong l) {
+  jvmtiError result = jvmti_env->RawMonitorEnter(LongToMonitor(l));
+  JvmtiErrorToException(env, result);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_rawMonitorExit(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jlong l) {
+  jvmtiError result = jvmti_env->RawMonitorExit(LongToMonitor(l));
+  JvmtiErrorToException(env, result);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_rawMonitorWait(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jlong l, jlong millis) {
+  jvmtiError result = jvmti_env->RawMonitorWait(LongToMonitor(l), millis);
+  JvmtiErrorToException(env, result);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_rawMonitorNotify(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jlong l) {
+  jvmtiError result = jvmti_env->RawMonitorNotify(LongToMonitor(l));
+  JvmtiErrorToException(env, result);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_rawMonitorNotifyAll(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jlong l) {
+  jvmtiError result = jvmti_env->RawMonitorNotifyAll(LongToMonitor(l));
+  JvmtiErrorToException(env, result);
+}
+
+// Don't do anything
+jint OnLoad(JavaVM* vm,
+            char* options ATTRIBUTE_UNUSED,
+            void* reserved ATTRIBUTE_UNUSED) {
+  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) {
+    printf("Unable to get jvmti env!\n");
+    return 1;
+  }
+  SetAllCapabilities(jvmti_env);
+  return 0;
+}
+
+}  // namespace Test923Monitors
+}  // namespace art
diff --git a/test/923-monitors/monitors.h b/test/923-monitors/monitors.h
new file mode 100644
index 0000000..14cd5cd
--- /dev/null
+++ b/test/923-monitors/monitors.h
@@ -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.
+ */
+
+#ifndef ART_TEST_923_MONITORS_MONITORS_H_
+#define ART_TEST_923_MONITORS_MONITORS_H_
+
+#include <jni.h>
+
+namespace art {
+namespace Test923Monitors {
+
+jint OnLoad(JavaVM* vm, char* options, void* reserved);
+
+}  // namespace Test923Monitors
+}  // namespace art
+
+#endif  // ART_TEST_923_MONITORS_MONITORS_H_
diff --git a/test/923-monitors/run b/test/923-monitors/run
new file mode 100755
index 0000000..4379349
--- /dev/null
+++ b/test/923-monitors/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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 "$@" --experimental agents \
+                   --experimental runtime-plugins \
+                   --jvmti
diff --git a/test/923-monitors/src/Main.java b/test/923-monitors/src/Main.java
new file mode 100644
index 0000000..e35ce12
--- /dev/null
+++ b/test/923-monitors/src/Main.java
@@ -0,0 +1,298 @@
+/*
+ * 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.
+ */
+
+import java.util.concurrent.CountDownLatch;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[1]);
+
+    doTest();
+  }
+
+  private static void doTest() throws Exception {
+    // Start a watchdog, to make sure on deadlocks etc the test dies.
+    startWatchdog();
+
+    sharedId = createRawMonitor();
+
+    output = new ArrayList<String>(100);
+
+    simpleTests(sharedId);
+
+    for (String s : output) {
+      System.out.println(s);
+    }
+    output.clear();
+
+    threadTests(sharedId);
+
+    destroyRawMonitor(sharedId);
+  }
+
+  private static void simpleTests(long id) {
+    unlock(id);  // Should fail.
+
+    lock(id);
+    unlock(id);
+    unlock(id);  // Should fail.
+
+    lock(id);
+    lock(id);
+    unlock(id);
+    unlock(id);
+    unlock(id);  // Should fail.
+
+    rawWait(id, 0);   // Should fail.
+    rawWait(id, -1);  // Should fail.
+    rawWait(id, 1);   // Should fail.
+
+    lock(id);
+    rawWait(id, 50);
+    unlock(id);
+    unlock(id);  // Should fail.
+
+    rawNotify(id);  // Should fail.
+    lock(id);
+    rawNotify(id);
+    unlock(id);
+    unlock(id);  // Should fail.
+
+    rawNotifyAll(id);  // Should fail.
+    lock(id);
+    rawNotifyAll(id);
+    unlock(id);
+    unlock(id);  // Should fail.
+  }
+
+  private static void threadTests(final long id) throws Exception {
+    final int N = 10;
+
+    final CountDownLatch waitLatch = new CountDownLatch(N);
+    final CountDownLatch wait2Latch = new CountDownLatch(1);
+
+    Runnable r = new Runnable() {
+      @Override
+      public void run() {
+        lock(id);
+        waitLatch.countDown();
+        rawWait(id, 0);
+        firstAwakened = Thread.currentThread();
+        appendToLog("Awakened");
+        unlock(id);
+        wait2Latch.countDown();
+      }
+    };
+
+    List<Thread> threads = new ArrayList<Thread>();
+    for (int i = 0; i < N; i++) {
+      Thread t = new Thread(r);
+      threads.add(t);
+      t.start();
+    }
+
+    // Wait till all threads have been started.
+    waitLatch.await();
+
+    // Hopefully enough time for all the threads to progress into wait.
+    Thread.yield();
+    Thread.sleep(500);
+
+    // Wake up one.
+    lock(id);
+    rawNotify(id);
+    unlock(id);
+
+    wait2Latch.await();
+
+    // Wait a little bit more to see stragglers. This is flaky - spurious wakeups could
+    // make the test fail.
+    Thread.yield();
+    Thread.sleep(500);
+    if (firstAwakened != null) {
+      firstAwakened.join();
+    }
+
+    // Wake up everyone else.
+    lock(id);
+    rawNotifyAll(id);
+    unlock(id);
+
+    // Wait for everyone to die.
+    for (Thread t : threads) {
+      t.join();
+    }
+
+    // Check threaded output.
+    Iterator<String> it = output.iterator();
+    // 1) Start with N locks and Waits.
+    {
+      int locks = 0;
+      int waits = 0;
+      for (int i = 0; i < 2*N; i++) {
+        String s = it.next();
+        if (s.equals("Lock")) {
+          locks++;
+        } else if (s.equals("Wait")) {
+          if (locks <= waits) {
+            System.out.println(output);
+            throw new RuntimeException("Wait before Lock");
+          }
+          waits++;
+        } else {
+          System.out.println(output);
+          throw new RuntimeException("Unexpected operation: " + s);
+        }
+      }
+    }
+
+    // 2) Expect Lock + Notify + Unlock.
+    expect("Lock", it, output);
+    expect("Notify", it, output);
+    expect("Unlock", it, output);
+
+    // 3) A single thread wakes up, runs, and dies.
+    expect("Awakened", it, output);
+    expect("Unlock", it, output);
+
+    // 4) Expect Lock + NotifyAll + Unlock.
+    expect("Lock", it, output);
+    expect("NotifyAll", it, output);
+    expect("Unlock", it, output);
+
+    // 5) N-1 threads wake up, run, and die.
+    {
+      int expectedUnlocks = 0;
+      int ops = 2 * (N-1);
+      for (int i = 0; i < ops; i++) {
+        String s = it.next();
+        if (s.equals("Awakened")) {
+          expectedUnlocks++;
+        } else if (s.equals("Unlock")) {
+          expectedUnlocks--;
+          if (expectedUnlocks < 0) {
+            System.out.println(output);
+            throw new RuntimeException("Unexpected unlock");
+          }
+        }
+      }
+    }
+
+    // 6) That should be it.
+    if (it.hasNext()) {
+      System.out.println(output);
+      throw new RuntimeException("Unexpected trailing output, starting with " + it.next());
+    }
+
+    output.clear();
+    System.out.println("Done");
+  }
+
+  private static void expect(String s, Iterator<String> it, List<String> output) {
+    String t = it.next();
+    if (!s.equals(t)) {
+      System.out.println(output);
+      throw new RuntimeException("Expected " + s + " but got " + t);
+    }
+  }
+
+  private static void lock(long id) {
+    appendToLog("Lock");
+    rawMonitorEnter(id);
+  }
+
+  private static void unlock(long id) {
+    appendToLog("Unlock");
+    try {
+      rawMonitorExit(id);
+    } catch (RuntimeException e) {
+      appendToLog(e.getMessage());
+    }
+  }
+
+  private static void rawWait(long id, long millis) {
+    appendToLog("Wait");
+    try {
+      rawMonitorWait(id, millis);
+    } catch (RuntimeException e) {
+      appendToLog(e.getMessage());
+    }
+  }
+
+  private static void rawNotify(long id) {
+    appendToLog("Notify");
+    try {
+      rawMonitorNotify(id);
+    } catch (RuntimeException e) {
+      appendToLog(e.getMessage());
+    }
+  }
+
+  private static void rawNotifyAll(long id) {
+    appendToLog("NotifyAll");
+    try {
+      rawMonitorNotifyAll(id);
+    } catch (RuntimeException e) {
+      appendToLog(e.getMessage());
+    }
+  }
+
+  private static synchronized void appendToLog(String s) {
+    output.add(s);
+  }
+
+  private static void startWatchdog() {
+    Runnable r = new Runnable() {
+      @Override
+      public void run() {
+        long start = System.currentTimeMillis();
+        // Give it a minute.
+        long end = 60 * 1000 + start;
+        for (;;) {
+          long delta = end - System.currentTimeMillis();
+          if (delta <= 0) {
+            break;
+          }
+
+          try {
+            Thread.currentThread().sleep(delta);
+          } catch (Exception e) {
+          }
+        }
+        System.out.println("TIMEOUT!");
+        System.exit(1);
+      }
+    };
+    Thread t = new Thread(r);
+    t.setDaemon(true);
+    t.start();
+  }
+
+  static volatile long sharedId;
+  static List<String> output;
+  static Thread firstAwakened;
+
+  private static native long createRawMonitor();
+  private static native void destroyRawMonitor(long id);
+  private static native void rawMonitorEnter(long id);
+  private static native void rawMonitorExit(long id);
+  private static native void rawMonitorWait(long id, long millis);
+  private static native void rawMonitorNotify(long id);
+  private static native void rawMonitorNotifyAll(long id);
+}
diff --git a/test/Android.bp b/test/Android.bp
index f6648d1..a223c3a 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -263,6 +263,7 @@
         "918-fields/fields.cc",
         "920-objects/objects.cc",
         "922-properties/properties.cc",
+        "923-monitors/monitors.cc",
     ],
     shared_libs: [
         "libbase",
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index a3f6864..fd3a897 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -295,6 +295,7 @@
   920-objects \
   921-hello-failure \
   922-properties \
+  923-monitors \
 
 ifneq (,$(filter target,$(TARGET_TYPES)))
   ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES), \
diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc
index e309a89..33e1321 100644
--- a/test/ti-agent/common_load.cc
+++ b/test/ti-agent/common_load.cc
@@ -40,6 +40,7 @@
 #include "918-fields/fields.h"
 #include "920-objects/objects.h"
 #include "922-properties/properties.h"
+#include "923-monitors/monitors.h"
 
 namespace art {
 
@@ -78,6 +79,7 @@
   { "920-objects", Test920Objects::OnLoad, nullptr },
   { "921-hello-failure", common_redefine::OnLoad, nullptr },
   { "922-properties", Test922Properties::OnLoad, nullptr },
+  { "923-monitors", Test923Monitors::OnLoad, nullptr },
 };
 
 static AgentLib* FindAgent(char* name) {