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) {