Add support for JVMTI monitor events.
Adds support for the JVMTI can_generate_monitor_events capability and
all associated events. This adds support for the
JVMTI_EVENT_MONITOR_WAIT, JVMTI_EVENT_MONITOR_WAITED,
JVMTI_EVENT_MONITOR_CONTENDED_ENTER, and
JVMTI_EVENT_MONITOR_CONTENDED_ENTERED events.
Bug: 65558434
Bug: 62821960
Bug: 34415266
Test: ./test.py --host -j50
Change-Id: I0fe8038e6c4249e77d37a67e5056b5d2a94b6f48
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
index d3f52f6..8e3cf21 100644
--- a/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -247,7 +247,7 @@
.can_generate_method_exit_events = 1,
.can_generate_all_class_hook_events = 0,
.can_generate_compiled_method_load_events = 0,
- .can_generate_monitor_events = 0,
+ .can_generate_monitor_events = 1,
.can_generate_vm_object_alloc_events = 1,
.can_generate_native_method_bind_events = 1,
.can_generate_garbage_collection_events = 1,
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index c41e15e..5911a03 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -31,6 +31,8 @@
#include "events-inl.h"
+#include <array>
+
#include "art_field-inl.h"
#include "art_jvmti.h"
#include "art_method-inl.h"
@@ -45,6 +47,7 @@
#include "jni_internal.h"
#include "mirror/class.h"
#include "mirror/object-inl.h"
+#include "monitor.h"
#include "nativehelper/ScopedLocalRef.h"
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
@@ -247,6 +250,127 @@
}
}
+template<typename Type>
+static Type AddLocalRef(art::JNIEnvExt* e, art::mirror::Object* obj)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return (obj == nullptr) ? nullptr : e->AddLocalReference<Type>(obj);
+}
+
+template<ArtJvmtiEvent kEvent, typename ...Args>
+static void RunEventCallback(EventHandler* handler,
+ art::Thread* self,
+ art::JNIEnvExt* jnienv,
+ Args... args)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ ScopedLocalRef<jthread> thread_jni(jnienv, AddLocalRef<jthread>(jnienv, self->GetPeer()));
+ art::StackHandleScope<1> hs(self);
+ art::Handle<art::mirror::Throwable> old_exception(hs.NewHandle(self->GetException()));
+ self->ClearException();
+ // Just give the event a good sized JNI frame. 100 should be fine.
+ jnienv->PushFrame(100);
+ {
+ // Need to do trampoline! :(
+ art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
+ handler->DispatchEvent<kEvent>(self,
+ static_cast<JNIEnv*>(jnienv),
+ thread_jni.get(),
+ args...);
+ }
+ jnienv->PopFrame();
+ if (!self->IsExceptionPending() && !old_exception.IsNull()) {
+ self->SetException(old_exception.Get());
+ }
+}
+
+class JvmtiMonitorListener : public art::MonitorCallback {
+ public:
+ explicit JvmtiMonitorListener(EventHandler* handler) : handler_(handler) {}
+
+ void MonitorContendedLocking(art::Monitor* m)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorContendedEnter)) {
+ art::Thread* self = art::Thread::Current();
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, m->GetObject()));
+ RunEventCallback<ArtJvmtiEvent::kMonitorContendedEnter>(
+ handler_,
+ self,
+ jnienv,
+ mon.get());
+ }
+ }
+
+ void MonitorContendedLocked(art::Monitor* m)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorContendedEntered)) {
+ art::Thread* self = art::Thread::Current();
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, m->GetObject()));
+ RunEventCallback<ArtJvmtiEvent::kMonitorContendedEntered>(
+ handler_,
+ self,
+ jnienv,
+ mon.get());
+ }
+ }
+
+ void ObjectWaitStart(art::Handle<art::mirror::Object> obj, int64_t timeout)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorWait)) {
+ art::Thread* self = art::Thread::Current();
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, obj.Get()));
+ RunEventCallback<ArtJvmtiEvent::kMonitorWait>(
+ handler_,
+ self,
+ jnienv,
+ mon.get(),
+ static_cast<jlong>(timeout));
+ }
+ }
+
+
+ // Our interpretation of the spec is that the JVMTI_EVENT_MONITOR_WAITED will be sent immediately
+ // after a thread has woken up from a sleep caused by a call to Object#wait. If the thread will
+ // never go to sleep (due to not having the lock, having bad arguments, or having an exception
+ // propogated from JVMTI_EVENT_MONITOR_WAIT) we will not send this event.
+ //
+ // This does not fully match the RI semantics. Specifically, we will not send the
+ // JVMTI_EVENT_MONITOR_WAITED event in one situation where the RI would, there was an exception in
+ // the JVMTI_EVENT_MONITOR_WAIT event but otherwise the call was fine. In that case the RI would
+ // send this event and return without going to sleep.
+ //
+ // See b/65558434 for more discussion.
+ void MonitorWaitFinished(art::Monitor* m, bool timeout)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorWaited)) {
+ art::Thread* self = art::Thread::Current();
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, m->GetObject()));
+ RunEventCallback<ArtJvmtiEvent::kMonitorWaited>(
+ handler_,
+ self,
+ jnienv,
+ mon.get(),
+ static_cast<jboolean>(timeout));
+ }
+ }
+
+ private:
+ EventHandler* handler_;
+};
+
+static void SetupMonitorListener(art::MonitorCallback* listener, bool enable) {
+ // We must not hold the mutator lock here, but if we're in FastJNI, for example, we might. For
+ // now, do a workaround: (possibly) acquire and release.
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ if (enable) {
+ art::Runtime::Current()->GetRuntimeCallbacks()->AddMonitorCallback(listener);
+ } else {
+ art::Runtime::Current()->GetRuntimeCallbacks()->RemoveMonitorCallback(listener);
+ }
+}
+
// Report GC pauses (see spec) as GARBAGE_COLLECTION_START and GARBAGE_COLLECTION_END.
class JvmtiGcPauseListener : public art::gc::GcPauseListener {
public:
@@ -301,33 +425,10 @@
}
}
-template<typename Type>
-static Type AddLocalRef(art::JNIEnvExt* e, art::mirror::Object* obj)
- REQUIRES_SHARED(art::Locks::mutator_lock_) {
- return (obj == nullptr) ? nullptr : e->AddLocalReference<Type>(obj);
-}
-
class JvmtiMethodTraceListener FINAL : public art::instrumentation::InstrumentationListener {
public:
explicit JvmtiMethodTraceListener(EventHandler* handler) : event_handler_(handler) {}
- template<ArtJvmtiEvent kEvent, typename ...Args>
- void RunEventCallback(art::Thread* self, art::JNIEnvExt* jnienv, Args... args)
- REQUIRES_SHARED(art::Locks::mutator_lock_) {
- ScopedLocalRef<jthread> thread_jni(jnienv, AddLocalRef<jthread>(jnienv, self->GetPeer()));
- // Just give the event a good sized JNI frame. 100 should be fine.
- jnienv->PushFrame(100);
- {
- // Need to do trampoline! :(
- art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
- event_handler_->DispatchEvent<kEvent>(self,
- static_cast<JNIEnv*>(jnienv),
- thread_jni.get(),
- args...);
- }
- jnienv->PopFrame();
- }
-
// Call-back for when a method is entered.
void MethodEntered(art::Thread* self,
art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
@@ -337,7 +438,8 @@
if (!method->IsRuntimeMethod() &&
event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodEntry)) {
art::JNIEnvExt* jnienv = self->GetJniEnv();
- RunEventCallback<ArtJvmtiEvent::kMethodEntry>(self,
+ RunEventCallback<ArtJvmtiEvent::kMethodEntry>(event_handler_,
+ self,
jnienv,
art::jni::EncodeArtMethod(method));
}
@@ -360,6 +462,7 @@
ScopedLocalRef<jobject> return_jobj(jnienv, AddLocalRef<jobject>(jnienv, return_value.Get()));
val.l = return_jobj.get();
RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(method),
@@ -386,6 +489,7 @@
// the union.
val.j = return_value.GetJ();
RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(method),
@@ -412,6 +516,7 @@
CHECK(!old_exception.IsNull());
self->ClearException();
RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(method),
@@ -442,11 +547,11 @@
jlocation location = static_cast<jlocation>(new_dex_pc);
// Step event is reported first according to the spec.
if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kSingleStep)) {
- RunEventCallback<ArtJvmtiEvent::kSingleStep>(self, jnienv, jmethod, location);
+ RunEventCallback<ArtJvmtiEvent::kSingleStep>(event_handler_, self, jnienv, jmethod, location);
}
// Next we do the Breakpoint events. The Dispatch code will filter the individual
if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kBreakpoint)) {
- RunEventCallback<ArtJvmtiEvent::kBreakpoint>(self, jnienv, jmethod, location);
+ RunEventCallback<ArtJvmtiEvent::kBreakpoint>(event_handler_, self, jnienv, jmethod, location);
}
}
@@ -464,7 +569,8 @@
ScopedLocalRef<jobject> fklass(jnienv,
AddLocalRef<jobject>(jnienv,
field->GetDeclaringClass().Ptr()));
- RunEventCallback<ArtJvmtiEvent::kFieldAccess>(self,
+ RunEventCallback<ArtJvmtiEvent::kFieldAccess>(event_handler_,
+ self,
jnienv,
art::jni::EncodeArtMethod(method),
static_cast<jlocation>(dex_pc),
@@ -492,6 +598,7 @@
jvalue val;
val.l = fval.get();
RunEventCallback<ArtJvmtiEvent::kFieldModification>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(method),
@@ -525,6 +632,7 @@
// the union.
val.j = field_value.GetJ();
RunEventCallback<ArtJvmtiEvent::kFieldModification>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(method),
@@ -545,6 +653,7 @@
art::JNIEnvExt* jnienv = self->GetJniEnv();
jboolean is_exception_pending = self->IsExceptionPending();
RunEventCallback<ArtJvmtiEvent::kFramePop>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(frame.GetMethod()),
@@ -639,6 +748,7 @@
ScopedLocalRef<jobject> exception(jnienv,
AddLocalRef<jobject>(jnienv, exception_object.Get()));
RunEventCallback<ArtJvmtiEvent::kException>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(method),
@@ -664,6 +774,7 @@
ScopedLocalRef<jobject> exception(jnienv,
AddLocalRef<jobject>(jnienv, exception_object.Get()));
RunEventCallback<ArtJvmtiEvent::kExceptionCatch>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(method),
@@ -759,6 +870,23 @@
instr->DeoptimizeEverything("jvmti-local-variable-access");
}
+bool EventHandler::OtherMonitorEventsEnabledAnywhere(ArtJvmtiEvent event) {
+ std::array<ArtJvmtiEvent, 4> events {
+ {
+ ArtJvmtiEvent::kMonitorContendedEnter,
+ ArtJvmtiEvent::kMonitorContendedEntered,
+ ArtJvmtiEvent::kMonitorWait,
+ ArtJvmtiEvent::kMonitorWaited
+ }
+ };
+ for (ArtJvmtiEvent e : events) {
+ if (e != event && IsEventEnabledAnywhere(e)) {
+ return true;
+ }
+ }
+ return false;
+}
+
// Handle special work for the given event type, if necessary.
void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) {
switch (event) {
@@ -799,7 +927,14 @@
case ArtJvmtiEvent::kExceptionCatch:
SetupTraceListener(method_trace_listener_.get(), event, enable);
return;
-
+ case ArtJvmtiEvent::kMonitorContendedEnter:
+ case ArtJvmtiEvent::kMonitorContendedEntered:
+ case ArtJvmtiEvent::kMonitorWait:
+ case ArtJvmtiEvent::kMonitorWaited:
+ if (!OtherMonitorEventsEnabledAnywhere(event)) {
+ SetupMonitorListener(monitor_listener_.get(), enable);
+ }
+ return;
default:
break;
}
@@ -928,6 +1063,7 @@
alloc_listener_.reset(new JvmtiAllocationListener(this));
gc_pause_listener_.reset(new JvmtiGcPauseListener(this));
method_trace_listener_.reset(new JvmtiMethodTraceListener(this));
+ monitor_listener_.reset(new JvmtiMonitorListener(this));
}
EventHandler::~EventHandler() {
diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h
index b49e80c..0d36c46 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -30,6 +30,7 @@
class JvmtiAllocationListener;
class JvmtiGcPauseListener;
class JvmtiMethodTraceListener;
+class JvmtiMonitorListener;
// an enum for ArtEvents. This differs from the JVMTI events only in that we distinguish between
// retransformation capable and incapable loading
@@ -212,6 +213,8 @@
void HandleEventType(ArtJvmtiEvent event, bool enable);
void HandleLocalAccessCapabilityAdded();
+ bool OtherMonitorEventsEnabledAnywhere(ArtJvmtiEvent event);
+
// List of all JvmTiEnv objects that have been created, in their creation order.
// NB Some elements might be null representing envs that have been deleted. They should be skipped
// anytime this list is used.
@@ -223,6 +226,7 @@
std::unique_ptr<JvmtiAllocationListener> alloc_listener_;
std::unique_ptr<JvmtiGcPauseListener> gc_pause_listener_;
std::unique_ptr<JvmtiMethodTraceListener> method_trace_listener_;
+ std::unique_ptr<JvmtiMonitorListener> monitor_listener_;
// True if frame pop has ever been enabled. Since we store pointers to stack frames we need to
// continue to listen to this event even if it has been disabled.
diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
index 27d01ea..d437e52 100644
--- a/openjdkjvmti/ti_thread.cc
+++ b/openjdkjvmti/ti_thread.cc
@@ -392,6 +392,8 @@
return JVMTI_JAVA_LANG_THREAD_STATE_NEW;
case art::ThreadState::kWaiting:
+ case art::ThreadState::kWaitingForTaskProcessor:
+ case art::ThreadState::kWaitingForLockInflation:
case art::ThreadState::kWaitingForGcToComplete:
case art::ThreadState::kWaitingPerformingGc:
case art::ThreadState::kWaitingForCheckPointsToRun:
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index af56810..2868180 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -2212,6 +2212,8 @@
case kTerminated:
return JDWP::TS_ZOMBIE;
case kTimedWaiting:
+ case kWaitingForTaskProcessor:
+ case kWaitingForLockInflation:
case kWaitingForCheckPointsToRun:
case kWaitingForDebuggerSend:
case kWaitingForDebuggerSuspension:
diff --git a/runtime/entrypoints/quick/quick_lock_entrypoints.cc b/runtime/entrypoints/quick/quick_lock_entrypoints.cc
index b4f945a..4bcce21 100644
--- a/runtime/entrypoints/quick/quick_lock_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_lock_entrypoints.cc
@@ -29,15 +29,22 @@
ThrowNullPointerException("Null reference used for synchronization (monitor-enter)");
return -1; // Failure.
} else {
- if (kIsDebugBuild) {
- obj = obj->MonitorEnter(self); // May block
- CHECK(self->HoldsLock(obj));
- CHECK(!self->IsExceptionPending());
+ obj = obj->MonitorEnter(self); // May block
+ DCHECK(self->HoldsLock(obj));
+ // Exceptions can be thrown by monitor event listeners. This is expected to be rare however.
+ if (UNLIKELY(self->IsExceptionPending())) {
+ // TODO Remove this DCHECK if we expand the use of monitor callbacks.
+ DCHECK(Runtime::Current()->HasLoadedPlugins())
+ << "Exceptions are only expected to be thrown by plugin code which doesn't seem to be "
+ << "loaded.";
+ // We need to get rid of the lock
+ bool unlocked = obj->MonitorExit(self);
+ DCHECK(unlocked);
+ return -1; // Failure.
} else {
- obj->MonitorEnter(self); // May block
+ DCHECK(self->HoldsLock(obj));
+ return 0; // Success.
}
- return 0; // Success.
- // Only possible exception is NPE and is handled before entry
}
}
diff --git a/runtime/gc/task_processor.cc b/runtime/gc/task_processor.cc
index 0704a68..e928644 100644
--- a/runtime/gc/task_processor.cc
+++ b/runtime/gc/task_processor.cc
@@ -34,14 +34,14 @@
}
void TaskProcessor::AddTask(Thread* self, HeapTask* task) {
- ScopedThreadStateChange tsc(self, kBlocked);
+ ScopedThreadStateChange tsc(self, kWaitingForTaskProcessor);
MutexLock mu(self, *lock_);
tasks_.insert(task);
cond_->Signal(self);
}
HeapTask* TaskProcessor::GetTask(Thread* self) {
- ScopedThreadStateChange tsc(self, kBlocked);
+ ScopedThreadStateChange tsc(self, kWaitingForTaskProcessor);
MutexLock mu(self, *lock_);
while (true) {
if (tasks_.empty()) {
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index 3ccab85..50bd7e7 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -65,9 +65,16 @@
static inline void DoMonitorEnter(Thread* self, ShadowFrame* frame, ObjPtr<mirror::Object> ref)
NO_THREAD_SAFETY_ANALYSIS
REQUIRES(!Roles::uninterruptible_) {
+ DCHECK(!ref.IsNull());
StackHandleScope<1> hs(self);
Handle<mirror::Object> h_ref(hs.NewHandle(ref));
h_ref->MonitorEnter(self);
+ DCHECK(self->HoldsLock(h_ref.Get()));
+ if (UNLIKELY(self->IsExceptionPending())) {
+ bool unlocked = h_ref->MonitorExit(self);
+ DCHECK(unlocked);
+ return;
+ }
if (kMonitorCounting && frame->GetMethod()->MustCountLocks()) {
frame->GetLockCountData().AddMonitor(self, h_ref.Get());
}
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index f96792d..d74cec3 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -2398,10 +2398,12 @@
ScopedObjectAccess soa(env);
ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(java_object);
o = o->MonitorEnter(soa.Self());
+ if (soa.Self()->HoldsLock(o)) {
+ soa.Env()->monitors.Add(o);
+ }
if (soa.Self()->IsExceptionPending()) {
return JNI_ERR;
}
- soa.Env()->monitors.Add(o);
return JNI_OK;
}
@@ -2409,11 +2411,14 @@
CHECK_NON_NULL_ARGUMENT_RETURN(java_object, JNI_ERR);
ScopedObjectAccess soa(env);
ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(java_object);
+ bool remove_mon = soa.Self()->HoldsLock(o);
o->MonitorExit(soa.Self());
+ if (remove_mon) {
+ soa.Env()->monitors.Remove(o);
+ }
if (soa.Self()->IsExceptionPending()) {
return JNI_ERR;
}
- soa.Env()->monitors.Remove(o);
return JNI_OK;
}
diff --git a/runtime/monitor.cc b/runtime/monitor.cc
index 80e6ad3..051e015 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -346,11 +346,31 @@
return TryLockLocked(self);
}
+// Asserts that a mutex isn't held when the class comes into and out of scope.
+class ScopedAssertNotHeld {
+ public:
+ ScopedAssertNotHeld(Thread* self, Mutex& mu) : self_(self), mu_(mu) {
+ mu_.AssertNotHeld(self_);
+ }
+
+ ~ScopedAssertNotHeld() {
+ mu_.AssertNotHeld(self_);
+ }
+
+ private:
+ Thread* const self_;
+ Mutex& mu_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedAssertNotHeld);
+};
+
+template <LockReason reason>
void Monitor::Lock(Thread* self) {
- MutexLock mu(self, monitor_lock_);
+ ScopedAssertNotHeld sanh(self, monitor_lock_);
+ bool called_monitors_callback = false;
+ monitor_lock_.Lock(self);
while (true) {
if (TryLockLocked(self)) {
- return;
+ break;
}
// Contended.
const bool log_contention = (lock_profiling_threshold_ != 0);
@@ -389,6 +409,12 @@
}
monitor_lock_.Unlock(self); // Let go of locks in order.
+ // Call the contended locking cb once and only once. Also only call it if we are locking for
+ // the first time, not during a Wait wakeup.
+ if (reason == LockReason::kForLock && !called_monitors_callback) {
+ called_monitors_callback = true;
+ Runtime::Current()->GetRuntimeCallbacks()->MonitorContendedLocking(this);
+ }
self->SetMonitorEnterObject(GetObject());
{
ScopedThreadSuspension tsc(self, kBlocked); // Change to blocked and give up mutator_lock_.
@@ -492,10 +518,10 @@
<< PrettyDuration(MsToNs(wait_ms));
}
LogContentionEvent(self,
- wait_ms,
- sample_percent,
- owners_method,
- owners_dex_pc);
+ wait_ms,
+ sample_percent,
+ owners_method,
+ owners_dex_pc);
}
}
}
@@ -508,8 +534,18 @@
monitor_lock_.Lock(self); // Reacquire locks in order.
--num_waiters_;
}
+ monitor_lock_.Unlock(self);
+ // We need to pair this with a single contended locking call. NB we match the RI behavior and call
+ // this even if MonitorEnter failed.
+ if (called_monitors_callback) {
+ CHECK(reason == LockReason::kForLock);
+ Runtime::Current()->GetRuntimeCallbacks()->MonitorContendedLocked(this);
+ }
}
+template void Monitor::Lock<LockReason::kForLock>(Thread* self);
+template void Monitor::Lock<LockReason::kForWait>(Thread* self);
+
static void ThrowIllegalMonitorStateExceptionF(const char* fmt, ...)
__attribute__((format(printf, 1, 2)));
@@ -690,6 +726,7 @@
AtraceMonitorLock(self, GetObject(), true /* is_wait */);
bool was_interrupted = false;
+ bool timed_out = false;
{
// Update thread state. If the GC wakes up, it'll ignore us, knowing
// that we won't touch any references in this state, and we'll check
@@ -718,7 +755,7 @@
self->GetWaitConditionVariable()->Wait(self);
} else {
DCHECK(why == kTimedWaiting || why == kSleeping) << why;
- self->GetWaitConditionVariable()->TimedWait(self, ms, ns);
+ timed_out = self->GetWaitConditionVariable()->TimedWait(self, ms, ns);
}
was_interrupted = self->IsInterrupted();
}
@@ -751,8 +788,11 @@
AtraceMonitorUnlock(); // End Wait().
+ // We just slept, tell the runtime callbacks about this.
+ Runtime::Current()->GetRuntimeCallbacks()->MonitorWaitFinished(this, timed_out);
+
// Re-acquire the monitor and lock.
- Lock(self);
+ Lock<LockReason::kForWait>(self);
monitor_lock_.Lock(self);
self->GetWaitMutex()->AssertNotHeld(self);
@@ -897,7 +937,7 @@
bool timed_out;
Thread* owner;
{
- ScopedThreadSuspension sts(self, kBlocked);
+ ScopedThreadSuspension sts(self, kWaitingForLockInflation);
owner = thread_list->SuspendThreadByThreadId(owner_thread_id,
SuspendReason::kInternal,
&timed_out);
@@ -989,10 +1029,10 @@
contention_count++;
Runtime* runtime = Runtime::Current();
if (contention_count <= runtime->GetMaxSpinsBeforeThinLockInflation()) {
- // TODO: Consider switching the thread state to kBlocked when we are yielding.
- // Use sched_yield instead of NanoSleep since NanoSleep can wait much longer than the
- // parameter you pass in. This can cause thread suspension to take excessively long
- // and make long pauses. See b/16307460.
+ // TODO: Consider switching the thread state to kWaitingForLockInflation when we are
+ // yielding. Use sched_yield instead of NanoSleep since NanoSleep can wait much longer
+ // than the parameter you pass in. This can cause thread suspension to take excessively
+ // long and make long pauses. See b/16307460.
// TODO: We should literally spin first, without sched_yield. Sched_yield either does
// nothing (at significant expense), or guarantees that we wait at least microseconds.
// If the owner is running, I would expect the median lock hold time to be hundreds
@@ -1098,7 +1138,16 @@
bool interruptShouldThrow, ThreadState why) {
DCHECK(self != nullptr);
DCHECK(obj != nullptr);
- LockWord lock_word = obj->GetLockWord(true);
+ StackHandleScope<1> hs(self);
+ Handle<mirror::Object> h_obj(hs.NewHandle(obj));
+
+ Runtime::Current()->GetRuntimeCallbacks()->ObjectWaitStart(h_obj, ms);
+ if (UNLIKELY(self->IsExceptionPending())) {
+ // See b/65558434 for information on handling of exceptions here.
+ return;
+ }
+
+ LockWord lock_word = h_obj->GetLockWord(true);
while (lock_word.GetState() != LockWord::kFatLocked) {
switch (lock_word.GetState()) {
case LockWord::kHashCode:
@@ -1115,8 +1164,8 @@
} else {
// We own the lock, inflate to enqueue ourself on the Monitor. May fail spuriously so
// re-load.
- Inflate(self, self, obj, 0);
- lock_word = obj->GetLockWord(true);
+ Inflate(self, self, h_obj.Get(), 0);
+ lock_word = h_obj->GetLockWord(true);
}
break;
}
@@ -1203,8 +1252,9 @@
if (monitor != nullptr) {
pretty_object = monitor->GetObject();
}
- } else if (state == kBlocked) {
- wait_message = " - waiting to lock ";
+ } else if (state == kBlocked || state == kWaitingForLockInflation) {
+ wait_message = (state == kBlocked) ? " - waiting to lock "
+ : " - waiting for lock inflation of ";
pretty_object = thread->GetMonitorEnterObject();
if (pretty_object != nullptr) {
if (kUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
diff --git a/runtime/monitor.h b/runtime/monitor.h
index 09faeba..d7aef34 100644
--- a/runtime/monitor.h
+++ b/runtime/monitor.h
@@ -31,6 +31,7 @@
#include "gc_root.h"
#include "lock_word.h"
#include "read_barrier_option.h"
+#include "runtime_callbacks.h"
#include "thread_state.h"
namespace art {
@@ -47,6 +48,11 @@
class Object;
} // namespace mirror
+enum class LockReason {
+ kForWait,
+ kForLock,
+};
+
class Monitor {
public:
// The default number of spins that are done before thread suspension is used to forcibly inflate
@@ -205,9 +211,11 @@
REQUIRES(monitor_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
+ template<LockReason reason = LockReason::kForLock>
void Lock(Thread* self)
REQUIRES(!monitor_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
+
bool Unlock(Thread* thread)
REQUIRES(!monitor_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc
index 4fbbb72..94007ff 100644
--- a/runtime/native/java_lang_Thread.cc
+++ b/runtime/native/java_lang_Thread.cc
@@ -86,6 +86,8 @@
case kWaiting: return kJavaWaiting;
case kStarting: return kJavaNew;
case kNative: return kJavaRunnable;
+ case kWaitingForTaskProcessor: return kJavaWaiting;
+ case kWaitingForLockInflation: return kJavaWaiting;
case kWaitingForGcToComplete: return kJavaWaiting;
case kWaitingPerformingGc: return kJavaWaiting;
case kWaitingForCheckPointsToRun: return kJavaWaiting;
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 4e84468..2eb4411 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -658,6 +658,10 @@
RuntimeCallbacks* GetRuntimeCallbacks();
+ bool HasLoadedPlugins() const {
+ return !plugins_.empty();
+ }
+
void InitThreadGroups(Thread* self);
void SetDumpGCPerformanceOnShutdown(bool value) {
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index 16d6c13..88d3f28 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -21,6 +21,7 @@
#include "art_method.h"
#include "base/macros.h"
#include "class_linker.h"
+#include "monitor.h"
#include "thread.h"
namespace art {
@@ -38,6 +39,38 @@
}
}
+void RuntimeCallbacks::MonitorContendedLocking(Monitor* m) {
+ for (MonitorCallback* cb : monitor_callbacks_) {
+ cb->MonitorContendedLocking(m);
+ }
+}
+
+void RuntimeCallbacks::MonitorContendedLocked(Monitor* m) {
+ for (MonitorCallback* cb : monitor_callbacks_) {
+ cb->MonitorContendedLocked(m);
+ }
+}
+
+void RuntimeCallbacks::ObjectWaitStart(Handle<mirror::Object> m, int64_t timeout) {
+ for (MonitorCallback* cb : monitor_callbacks_) {
+ cb->ObjectWaitStart(m, timeout);
+ }
+}
+
+void RuntimeCallbacks::MonitorWaitFinished(Monitor* m, bool timeout) {
+ for (MonitorCallback* cb : monitor_callbacks_) {
+ cb->MonitorWaitFinished(m, timeout);
+ }
+}
+
+void RuntimeCallbacks::AddMonitorCallback(MonitorCallback* cb) {
+ monitor_callbacks_.push_back(cb);
+}
+
+void RuntimeCallbacks::RemoveMonitorCallback(MonitorCallback* cb) {
+ Remove(cb, &monitor_callbacks_);
+}
+
void RuntimeCallbacks::RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) {
Remove(cb, &thread_callbacks_);
}
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index e8f1824..fa686d3 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -29,12 +29,14 @@
namespace mirror {
class Class;
class ClassLoader;
+class Object;
} // namespace mirror
class ArtMethod;
class ClassLoadCallback;
class Thread;
class MethodCallback;
+class Monitor;
class ThreadLifecycleCallback;
// Note: RuntimeCallbacks uses the mutator lock to synchronize the callback lists. A thread must
@@ -73,6 +75,25 @@
virtual void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
};
+class MonitorCallback {
+ public:
+ // Called just before the thread goes to sleep to wait for the monitor to become unlocked.
+ virtual void MonitorContendedLocking(Monitor* mon) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+ // Called just after the monitor has been successfully acquired when it was already locked.
+ virtual void MonitorContendedLocked(Monitor* mon) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+ // Called on entry to the Object#wait method regardless of whether or not the call is valid.
+ virtual void ObjectWaitStart(Handle<mirror::Object> obj, int64_t millis_timeout)
+ REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+
+ // Called just after the monitor has woken up from going to sleep for a wait(). At this point the
+ // thread does not possess a lock on the monitor. This will only be called for threads wait calls
+ // where the thread did (or at least could have) gone to sleep.
+ virtual void MonitorWaitFinished(Monitor* m, bool timed_out)
+ REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+
+ virtual ~MonitorCallback() {}
+};
+
class RuntimeCallbacks {
public:
void AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) REQUIRES(Locks::mutator_lock_);
@@ -120,6 +141,16 @@
/*out*/void** new_implementation)
REQUIRES_SHARED(Locks::mutator_lock_);
+ void MonitorContendedLocking(Monitor* m) REQUIRES_SHARED(Locks::mutator_lock_);
+ void MonitorContendedLocked(Monitor* m) REQUIRES_SHARED(Locks::mutator_lock_);
+ void ObjectWaitStart(Handle<mirror::Object> m, int64_t timeout)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ void MonitorWaitFinished(Monitor* m, bool timed_out)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ void AddMonitorCallback(MonitorCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
+ void RemoveMonitorCallback(MonitorCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
+
private:
std::vector<ThreadLifecycleCallback*> thread_callbacks_
GUARDED_BY(Locks::mutator_lock_);
@@ -131,6 +162,8 @@
GUARDED_BY(Locks::mutator_lock_);
std::vector<MethodCallback*> method_callbacks_
GUARDED_BY(Locks::mutator_lock_);
+ std::vector<MonitorCallback*> monitor_callbacks_
+ GUARDED_BY(Locks::mutator_lock_);
};
} // namespace art
diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc
index ac2ed9e..ef17258 100644
--- a/runtime/runtime_callbacks_test.cc
+++ b/runtime/runtime_callbacks_test.cc
@@ -22,6 +22,7 @@
#include <initializer_list>
#include <memory>
+#include <mutex>
#include <string>
#include "jni.h"
@@ -29,12 +30,14 @@
#include "art_method-inl.h"
#include "base/mutex.h"
#include "class_linker.h"
+#include "class_reference.h"
#include "common_runtime_test.h"
#include "handle.h"
#include "handle_scope-inl.h"
#include "mem_map.h"
#include "mirror/class-inl.h"
#include "mirror/class_loader.h"
+#include "monitor.h"
#include "nativehelper/ScopedLocalRef.h"
#include "obj_ptr.h"
#include "runtime.h"
@@ -433,4 +436,87 @@
ASSERT_EQ(1u, cb_.death_seen);
}
+class MonitorWaitCallbacksTest : public RuntimeCallbacksTest {
+ protected:
+ void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+ Runtime::Current()->GetRuntimeCallbacks()->AddMonitorCallback(&cb_);
+ }
+ void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+ Runtime::Current()->GetRuntimeCallbacks()->RemoveMonitorCallback(&cb_);
+ }
+
+ struct Callback : public MonitorCallback {
+ bool IsInterestingObject(mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (!obj->IsClass()) {
+ return false;
+ }
+ std::lock_guard<std::mutex> lock(ref_guard_);
+ mirror::Class* k = obj->AsClass();
+ ClassReference test = { &k->GetDexFile(), k->GetDexClassDefIndex() };
+ return ref_ == test;
+ }
+
+ void SetInterestingObject(mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ std::lock_guard<std::mutex> lock(ref_guard_);
+ mirror::Class* k = obj->AsClass();
+ ref_ = { &k->GetDexFile(), k->GetDexClassDefIndex() };
+ }
+
+ void MonitorContendedLocking(Monitor* mon ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) { }
+
+ void MonitorContendedLocked(Monitor* mon ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) { }
+
+ void ObjectWaitStart(Handle<mirror::Object> obj, int64_t millis ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (IsInterestingObject(obj.Get())) {
+ saw_wait_start_ = true;
+ }
+ }
+
+ void MonitorWaitFinished(Monitor* m, bool timed_out ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (IsInterestingObject(m->GetObject())) {
+ saw_wait_finished_ = true;
+ }
+ }
+
+ std::mutex ref_guard_;
+ ClassReference ref_ = {nullptr, 0};
+ bool saw_wait_start_ = false;
+ bool saw_wait_finished_ = false;
+ };
+
+ Callback cb_;
+};
+
+// TODO It would be good to have more tests for this but due to the multi-threaded nature of the
+// callbacks this is difficult. For now the run-tests 1931 & 1932 should be sufficient.
+TEST_F(MonitorWaitCallbacksTest, WaitUnlocked) {
+ ASSERT_FALSE(cb_.saw_wait_finished_);
+ ASSERT_FALSE(cb_.saw_wait_start_);
+ {
+ Thread* self = Thread::Current();
+ self->TransitionFromSuspendedToRunnable();
+ bool started = runtime_->Start();
+ ASSERT_TRUE(started);
+ {
+ ScopedObjectAccess soa(self);
+ cb_.SetInterestingObject(
+ soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections).Ptr());
+ Monitor::Wait(
+ self,
+ // Just a random class
+ soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections).Ptr(),
+ /*ms*/0,
+ /*ns*/0,
+ /*interruptShouldThrow*/false,
+ /*why*/kWaiting);
+ }
+ }
+ ASSERT_TRUE(cb_.saw_wait_start_);
+ ASSERT_FALSE(cb_.saw_wait_finished_);
+}
+
} // namespace art
diff --git a/runtime/thread_state.h b/runtime/thread_state.h
index 8f2f70f..8edfeec 100644
--- a/runtime/thread_state.h
+++ b/runtime/thread_state.h
@@ -29,6 +29,8 @@
kSleeping, // TIMED_WAITING TS_SLEEPING in Thread.sleep()
kBlocked, // BLOCKED TS_MONITOR blocked on a monitor
kWaiting, // WAITING TS_WAIT in Object.wait()
+ kWaitingForLockInflation, // WAITING TS_WAIT blocked inflating a thin-lock
+ kWaitingForTaskProcessor, // WAITING TS_WAIT blocked waiting for taskProcessor
kWaitingForGcToComplete, // WAITING TS_WAIT blocked waiting for GC
kWaitingForCheckPointsToRun, // WAITING TS_WAIT GC waiting for checkpoints to run
kWaitingPerformingGc, // WAITING TS_WAIT performing GC
diff --git a/test/1930-monitor-info/src/art/Monitors.java b/test/1930-monitor-info/src/art/Monitors.java
index 26f7718..f6a99fd 100644
--- a/test/1930-monitor-info/src/art/Monitors.java
+++ b/test/1930-monitor-info/src/art/Monitors.java
@@ -16,12 +16,24 @@
package art;
-import java.util.Arrays;
-import java.util.Objects;
+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;
public NamedLock(String name) {
@@ -68,5 +80,220 @@
}
public static native MonitorUsage getObjectMonitorUsage(Object monitor);
+
+ 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 Object 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(Object lock) {
+ this(lock, 10 * 1000);
+ }
+ public LockController(Object 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 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.notify();
+ break;
+ case NOTIFY_ALL:
+ lock.notifyAll();
+ break;
+ case TIMED_WAIT:
+ lock.wait(timeout);
+ break;
+ case WAIT:
+ lock.wait();
+ 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/1931-monitor-events/expected.txt b/test/1931-monitor-events/expected.txt
new file mode 100644
index 0000000..33a9bd3
--- /dev/null
+++ b/test/1931-monitor-events/expected.txt
@@ -0,0 +1,29 @@
+Testing contended locking.
+Locker thread 1 for NamedLock[Lock testLock] contended-LOCKING NamedLock[Lock testLock]
+Locker thread 1 for NamedLock[Lock testLock] LOCKED NamedLock[Lock testLock]
+Testing monitor wait.
+Locker thread 2 for NamedLock[Lock testWait] start-monitor-wait NamedLock[Lock testWait] timeout: 0
+Locker thread 2 for NamedLock[Lock testWait] monitor-waited NamedLock[Lock testWait] timed_out: false
+Testing monitor timed wait.
+Locker thread 4 for NamedLock[Lock testTimedWait] start-monitor-wait NamedLock[Lock testTimedWait] timeout: 3600000
+Locker thread 4 for NamedLock[Lock testTimedWait] monitor-waited NamedLock[Lock testTimedWait] timed_out: false
+Testing monitor timed with timeout.
+Waiting for 10 seconds.
+Locker thread 6 for NamedLock[Lock testTimedWaitTimeout] start-monitor-wait NamedLock[Lock testTimedWaitTimeout] timeout: 10000
+Locker thread 6 for NamedLock[Lock testTimedWaitTimeout] monitor-waited NamedLock[Lock testTimedWaitTimeout] timed_out: true
+Wait finished with timeout.
+Waiting on an unlocked monitor.
+Unlocked wait thread: start-monitor-wait NamedLock[Lock testUnlockedWait] timeout: 0
+Caught exception: java.lang.reflect.InvocationTargetException
+ Caused by: class java.lang.IllegalMonitorStateException
+Waiting with an illegal argument (negative timeout)
+Locker thread 7 for NamedLock[Lock testIllegalWait] start-monitor-wait NamedLock[Lock testIllegalWait] timeout: -100
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: Got an error while performing action TIMED_WAIT
+ Caused by: class java.lang.IllegalArgumentException
+Interrupt a monitor being waited on.
+Locker thread 8 for NamedLock[Lock testInteruptWait] start-monitor-wait NamedLock[Lock testInteruptWait] timeout: 0
+Locker thread 8 for NamedLock[Lock testInteruptWait] monitor-waited NamedLock[Lock testInteruptWait] timed_out: false
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: Got an error while performing action WAIT
+ Caused by: class java.lang.InterruptedException
diff --git a/test/1931-monitor-events/info.txt b/test/1931-monitor-events/info.txt
new file mode 100644
index 0000000..ae07c53
--- /dev/null
+++ b/test/1931-monitor-events/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that the basic monitor-events work as we expect them to.
diff --git a/test/1931-monitor-events/run b/test/1931-monitor-events/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1931-monitor-events/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/1931-monitor-events/src/Main.java b/test/1931-monitor-events/src/Main.java
new file mode 100644
index 0000000..81c9d2c
--- /dev/null
+++ b/test/1931-monitor-events/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.Test1931.run();
+ }
+}
diff --git a/test/1931-monitor-events/src/art/Monitors.java b/test/1931-monitor-events/src/art/Monitors.java
new file mode 100644
index 0000000..f6a99fd
--- /dev/null
+++ b/test/1931-monitor-events/src/art/Monitors.java
@@ -0,0 +1,299 @@
+/*
+ * 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;
+ public NamedLock(String name) {
+ this.name = name;
+ }
+ public String toString() {
+ return String.format("NamedLock[%s]", name);
+ }
+ }
+
+ 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 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 Object 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(Object lock) {
+ this(lock, 10 * 1000);
+ }
+ public LockController(Object 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 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.notify();
+ break;
+ case NOTIFY_ALL:
+ lock.notifyAll();
+ break;
+ case TIMED_WAIT:
+ lock.wait(timeout);
+ break;
+ case WAIT:
+ lock.wait();
+ 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/1931-monitor-events/src/art/Test1931.java b/test/1931-monitor-events/src/art/Test1931.java
new file mode 100644
index 0000000..ccefede
--- /dev/null
+++ b/test/1931-monitor-events/src/art/Test1931.java
@@ -0,0 +1,198 @@
+/*
+ * 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.Arrays;
+import java.util.Objects;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.*;
+import java.util.ListIterator;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class Test1931 {
+ public static void printStackTrace(Throwable t) {
+ System.out.println("Caught exception: " + t);
+ for (Throwable c = t.getCause(); c != null; c = c.getCause()) {
+ System.out.println("\tCaused by: " +
+ (Test1931.class.getPackage().equals(c.getClass().getPackage())
+ ? c.toString() : c.getClass().toString()));
+ }
+ }
+
+ public static void handleMonitorEnter(Thread thd, Object lock) {
+ System.out.println(thd.getName() + " contended-LOCKING " + lock);
+ }
+
+ public static void handleMonitorEntered(Thread thd, Object lock) {
+ System.out.println(thd.getName() + " LOCKED " + lock);
+ }
+ public static void handleMonitorWait(Thread thd, Object lock, long timeout) {
+ System.out.println(thd.getName() + " start-monitor-wait " + lock + " timeout: " + timeout);
+ }
+
+ public static void handleMonitorWaited(Thread thd, Object lock, boolean timed_out) {
+ System.out.println(thd.getName() + " monitor-waited " + lock + " timed_out: " + timed_out);
+ }
+
+ public static void run() throws Exception {
+ Monitors.setupMonitorEvents(
+ Test1931.class,
+ Test1931.class.getDeclaredMethod("handleMonitorEnter", Thread.class, Object.class),
+ Test1931.class.getDeclaredMethod("handleMonitorEntered", Thread.class, Object.class),
+ Test1931.class.getDeclaredMethod("handleMonitorWait",
+ Thread.class, Object.class, Long.TYPE),
+ Test1931.class.getDeclaredMethod("handleMonitorWaited",
+ Thread.class, Object.class, Boolean.TYPE),
+ Monitors.NamedLock.class,
+ null);
+
+ System.out.println("Testing contended locking.");
+ testLock(new Monitors.NamedLock("Lock testLock"));
+
+ System.out.println("Testing monitor wait.");
+ testWait(new Monitors.NamedLock("Lock testWait"));
+
+ System.out.println("Testing monitor timed wait.");
+ testTimedWait(new Monitors.NamedLock("Lock testTimedWait"));
+
+ System.out.println("Testing monitor timed with timeout.");
+ testTimedWaitTimeout(new Monitors.NamedLock("Lock testTimedWaitTimeout"));
+
+ // TODO It would be good (but annoying) to do this with jasmin/smali in order to test if it's
+ // different without the reflection.
+ System.out.println("Waiting on an unlocked monitor.");
+ testUnlockedWait(new Monitors.NamedLock("Lock testUnlockedWait"));
+
+ System.out.println("Waiting with an illegal argument (negative timeout)");
+ testIllegalWait(new Monitors.NamedLock("Lock testIllegalWait"));
+
+ System.out.println("Interrupt a monitor being waited on.");
+ testInteruptWait(new Monitors.NamedLock("Lock testInteruptWait"));
+ }
+
+ public static void testInteruptWait(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+ try {
+ controller1.interruptWorker();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ System.out.println("No Exception thrown!");
+ } catch (Monitors.TestException e) {
+ printStackTrace(e);
+ }
+ controller1.DoCleanup();
+ }
+
+ public static void testIllegalWait(final Monitors.NamedLock lk) throws Exception {
+ Monitors.LockController controller1 = new Monitors.LockController(lk, /*timed_wait time*/-100);
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ try {
+ controller1.DoTimedWait();
+ controller1.waitForNotifySleep();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ System.out.println("No Exception thrown!");
+ } catch (Monitors.TestException e) {
+ printStackTrace(e);
+ }
+ controller1.DoCleanup();
+ }
+
+ public static void testUnlockedWait(final Monitors.NamedLock lk) throws Exception {
+ synchronized (lk) {
+ Thread thd = new Thread(() -> {
+ try {
+ Method m = Object.class.getDeclaredMethod("wait");
+ m.invoke(lk);
+ } catch (Exception e) {
+ printStackTrace(e);
+ }
+ }, "Unlocked wait thread:");
+ thd.start();
+ thd.join();
+ }
+ }
+
+ public static void testLock(Monitors.NamedLock lk) throws Exception {
+ Monitors.LockController controller1 = new Monitors.LockController(lk);
+ Monitors.LockController controller2 = new Monitors.LockController(lk);
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller2.DoLock();
+ if (controller2.IsLocked()) {
+ throw new Exception("c2 was able to gain lock while it was held by c1");
+ }
+ controller2.waitForContendedSleep();
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ }
+
+ public static void testWait(Monitors.NamedLock lk) throws Exception {
+ Monitors.LockController controller1 = new Monitors.LockController(lk);
+ Monitors.LockController controller2 = new Monitors.LockController(lk);
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoNotifyAll();
+ controller2.DoUnlock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ }
+
+ public static void testTimedWait(Monitors.NamedLock lk) throws Exception {
+ // Time to wait (1 hour). We will wake it up before timeout.
+ final long millis = 60l * 60l * 1000l;
+ Monitors.LockController controller1 = new Monitors.LockController(lk, millis);
+ Monitors.LockController controller2 = new Monitors.LockController(lk);
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoTimedWait();
+ controller1.waitForNotifySleep();
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoNotifyAll();
+ controller2.DoUnlock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ }
+
+ public static void testTimedWaitTimeout(Monitors.NamedLock lk) throws Exception {
+ // Time to wait (10 seconds). We will wait for the timeout.
+ final long millis = 10l * 1000l;
+ Monitors.LockController controller1 = new Monitors.LockController(lk, millis);
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ System.out.println("Waiting for 10 seconds.");
+ controller1.DoTimedWait();
+ controller1.waitForNotifySleep();
+ controller1.DoUnlock();
+ System.out.println("Wait finished with timeout.");
+ }
+}
diff --git a/test/1932-monitor-events-misc/check b/test/1932-monitor-events-misc/check
new file mode 100644
index 0000000..8a84388
--- /dev/null
+++ b/test/1932-monitor-events-misc/check
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# 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.
+
+# The RI sends an extra event that art doesn't. Add it to the expected output.
+if [[ "$TEST_RUNTIME" == "jvm" ]]; then
+ patch -p0 expected.txt < jvm-expected.patch >/dev/null
+fi
+
+./default-check "$@"
diff --git a/test/1932-monitor-events-misc/expected.txt b/test/1932-monitor-events-misc/expected.txt
new file mode 100644
index 0000000..b33aa7d
--- /dev/null
+++ b/test/1932-monitor-events-misc/expected.txt
@@ -0,0 +1,104 @@
+Testing contended locking where lock is released before callback ends.
+Locker thread 1 for NamedLock[Lock testLockUncontend] contended-LOCKING NamedLock[Lock testLockUncontend]
+Releasing NamedLock[Lock testLockUncontend] during monitorEnter event.
+Locker thread 1 for NamedLock[Lock testLockUncontend] LOCKED NamedLock[Lock testLockUncontend]
+Testing throwing exceptions in monitor_enter
+Locker thread 3 for NamedLock[Lock testLockThrowEnter] contended-LOCKING NamedLock[Lock testLockThrowEnter]
+Throwing exception in MonitorEnter
+Locker thread 3 for NamedLock[Lock testLockThrowEnter] LOCKED NamedLock[Lock testLockThrowEnter]
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during monitorEnter of NamedLock[Lock testLockThrowEnter]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowEnter], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exceptions in monitor_entered
+Locker thread 5 for NamedLock[Lock testLockThrowEntered] contended-LOCKING NamedLock[Lock testLockThrowEntered]
+Locker thread 5 for NamedLock[Lock testLockThrowEntered] LOCKED NamedLock[Lock testLockThrowEntered]
+Throwing exception in MonitorEntered
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[Lock testLockThrowEntered]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowEntered], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exceptions in both monitorEnter & MonitorEntered
+Locker thread 7 for NamedLock[Lock testLockThrowBoth] contended-LOCKING NamedLock[Lock testLockThrowBoth]
+Throwing exception in MonitorEnter
+Locker thread 7 for NamedLock[Lock testLockThrowBoth] LOCKED NamedLock[Lock testLockThrowBoth]
+Throwing exception in MonitorEntered
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[Lock testLockThrowBoth]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowBoth], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWait event
+Locker thread 8 for NamedLock[Lock testThrowWait] start-monitor-wait NamedLock[Lock testThrowWait] timeout: 0
+Throwing exception in MonitorWait
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during MonitorWait of NamedLock[Lock testThrowWait]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWait], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWait event with illegal aruments
+Locker thread 9 for NamedLock[Lock testThrowIllegalWait] start-monitor-wait NamedLock[Lock testThrowIllegalWait] timeout: -100000
+Throwing exception in MonitorWait timeout = -100000
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during monitorWait of NamedLock[Lock testThrowIllegalWait]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowIllegalWait], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWaited event
+Locker thread 10 for NamedLock[Lock testThrowWaited] start-monitor-wait NamedLock[Lock testThrowWaited] timeout: 0
+Locker thread 10 for NamedLock[Lock testThrowWaited] monitor-waited NamedLock[Lock testThrowWaited] timed_out: false
+Throwing exception in MonitorWaited
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaited]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaited], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWaited event caused by timeout
+Locker thread 12 for NamedLock[Lock testThrowWaitedTimeout] start-monitor-wait NamedLock[Lock testThrowWaitedTimeout] timeout: 5000
+Locker thread 12 for NamedLock[Lock testThrowWaitedTimeout] monitor-waited NamedLock[Lock testThrowWaitedTimeout] timed_out: true
+Throwing exception in MonitorWaited
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaitedTimeout]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaitedTimeout], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWaited event caused by interrupt
+Locker thread 13 for NamedLock[Lock testThrowWaitedInterrupt] start-monitor-wait NamedLock[Lock testThrowWaitedInterrupt] timeout: 0
+Locker thread 13 for NamedLock[Lock testThrowWaitedInterrupt] monitor-waited NamedLock[Lock testThrowWaitedInterrupt] timed_out: false
+Throwing exception in MonitorWaited
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaitedInterrupt]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaitedInterrupt], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing ObjectMonitorInfo inside of events
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] contended-LOCKING NamedLock[Lock testMonitorInfoInEvents]
+Monitor usage in MonitorEnter: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 14 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] }
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] LOCKED NamedLock[Lock testMonitorInfoInEvents]
+Monitor usage in MonitorEntered: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] }
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] start-monitor-wait NamedLock[Lock testMonitorInfoInEvents] timeout: 0
+Monitor usage in MonitorWait: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] }
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] monitor-waited NamedLock[Lock testMonitorInfoInEvents] timed_out: false
+Monitor usage in MonitorWaited: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing that the monitor can be stolen during the MonitorWaited event.
+Locker thread 17 for NamedLock[test testWaitEnterInterleaving] start-monitor-wait NamedLock[test testWaitEnterInterleaving] timeout: 0
+Locker thread 17 for NamedLock[test testWaitEnterInterleaving] monitor-waited NamedLock[test testWaitEnterInterleaving] timed_out: false
+locking controller3 in controller2 MonitorWaited event
+Controller3 now holds the lock the monitor wait will try to re-acquire
+Testing that we can lock and release the monitor in the MonitorWait event
+Locker thread 20 for NamedLock[test testWaitMonitorEnter] start-monitor-wait NamedLock[test testWaitMonitorEnter] timeout: 0
+In wait monitor usage: MonitorUsage{ monitor: NamedLock[test testWaitMonitorEnter], owner: Locker thread 20 for NamedLock[test testWaitMonitorEnter], entryCount: 1, waiters: [], notify_waiters: [] }
+In wait monitor usage sync: MonitorUsage{ monitor: NamedLock[test testWaitMonitorEnter], owner: Locker thread 20 for NamedLock[test testWaitMonitorEnter], entryCount: 2, waiters: [], notify_waiters: [] }
+Locker thread 20 for NamedLock[test testWaitMonitorEnter] monitor-waited NamedLock[test testWaitMonitorEnter] timed_out: false
+Testing that we can lock and release the monitor in the MonitorWaited event
+Locker thread 22 for NamedLock[test testWaitedMonitorEnter] start-monitor-wait NamedLock[test testWaitedMonitorEnter] timeout: 0
+Locker thread 22 for NamedLock[test testWaitedMonitorEnter] monitor-waited NamedLock[test testWaitedMonitorEnter] timed_out: false
+In waited monitor usage: MonitorUsage{ monitor: NamedLock[test testWaitedMonitorEnter], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+In waited monitor usage sync: MonitorUsage{ monitor: NamedLock[test testWaitedMonitorEnter], owner: Locker thread 22 for NamedLock[test testWaitedMonitorEnter], entryCount: 1, waiters: [], notify_waiters: [] }
+Testing we can perform recursive lock in MonitorEntered
+Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock] contended-LOCKING NamedLock[test testRecursiveMontiorEnteredLock]
+Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock] LOCKED NamedLock[test testRecursiveMontiorEnteredLock]
+In MonitorEntered usage: MonitorUsage{ monitor: NamedLock[test testRecursiveMontiorEnteredLock], owner: Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock], entryCount: 1, waiters: [], notify_waiters: [] }
+In MonitorEntered sync: MonitorUsage{ monitor: NamedLock[test testRecursiveMontiorEnteredLock], owner: Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock], entryCount: 2, waiters: [], notify_waiters: [] }
+Testing the lock state if MonitorEnter throws in a native method
+NativeLockStateThrowEnter thread contended-LOCKING NamedLock[test testNativeLockStateThrowEnter]
+Unlocking controller1 in MonitorEnter
+Throwing exception in MonitorEnter
+NativeLockStateThrowEnter thread LOCKED NamedLock[test testNativeLockStateThrowEnter]
+MonitorEnter returned: -1
+Lock state is: MonitorUsage{ monitor: NamedLock[test testNativeLockStateThrowEnter], owner: NativeLockStateThrowEnter thread, entryCount: 1, waiters: [], notify_waiters: [] }
+Caught exception: art.Monitors$TestException: throwing exception during monitorEnter of NamedLock[test testNativeLockStateThrowEnter]
+Testing the lock state if MonitorEntered throws in a native method
+NativeLockStateThrowEntered thread contended-LOCKING NamedLock[test testNativeLockStateThrowEntered]
+Unlocking controller1 in MonitorEnter
+NativeLockStateThrowEntered thread LOCKED NamedLock[test testNativeLockStateThrowEntered]
+Throwing exception in MonitorEntered
+MonitorEnter returned: -1
+Lock state is: MonitorUsage{ monitor: NamedLock[test testNativeLockStateThrowEntered], owner: NativeLockStateThrowEntered thread, entryCount: 1, waiters: [], notify_waiters: [] }
+Caught exception: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[test testNativeLockStateThrowEntered]
diff --git a/test/1932-monitor-events-misc/info.txt b/test/1932-monitor-events-misc/info.txt
new file mode 100644
index 0000000..674ef56
--- /dev/null
+++ b/test/1932-monitor-events-misc/info.txt
@@ -0,0 +1,4 @@
+Tests jvmti monitor events in odd situations.
+
+Checks that the JVMTI monitor events are correctly dispatched and handled for
+many odd situations.
diff --git a/test/1932-monitor-events-misc/jvm-expected.patch b/test/1932-monitor-events-misc/jvm-expected.patch
new file mode 100644
index 0000000..f6b2285
--- /dev/null
+++ b/test/1932-monitor-events-misc/jvm-expected.patch
@@ -0,0 +1,2 @@
+29a30
+> Locker thread 8 for NamedLock[Lock testThrowWait] monitor-waited NamedLock[Lock testThrowWait] timed_out: false
diff --git a/test/1932-monitor-events-misc/monitor_misc.cc b/test/1932-monitor-events-misc/monitor_misc.cc
new file mode 100644
index 0000000..842c550
--- /dev/null
+++ b/test/1932-monitor-events-misc/monitor_misc.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 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 Test1932MonitorEventsMisc {
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1932_doNativeLockPrint(JNIEnv* env,
+ jclass klass,
+ jobject lock) {
+ // jobject atomic_boolean) {
+ // ScopedLocalRef<jclass> atomic_klass(env, env->FindClass("java/util/concurrent/AtomicBoolean"));
+ // if (env->ExceptionCheck()) {
+ // return;
+ // }
+ // jmethodID atomic_set = env->GetMethodID(atomic_klass.get(), "set", "(z)V");
+ jmethodID print_state = env->GetStaticMethodID(
+ klass, "printLockState", "(Lart/Monitors$NamedLock;Ljava/lang/Object;I)V");
+ if (env->ExceptionCheck()) {
+ return;
+ }
+ jint res = env->MonitorEnter(lock);
+ ScopedLocalRef<jobject> exc(env, env->ExceptionOccurred());
+ env->ExceptionClear();
+ env->CallStaticVoidMethod(klass, print_state, lock, exc.get(), res);
+ env->MonitorExit(lock);
+}
+
+} // namespace Test1932MonitorEventsMisc
+} // namespace art
diff --git a/test/1932-monitor-events-misc/run b/test/1932-monitor-events-misc/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1932-monitor-events-misc/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/1932-monitor-events-misc/src/Main.java b/test/1932-monitor-events-misc/src/Main.java
new file mode 100644
index 0000000..0100074
--- /dev/null
+++ b/test/1932-monitor-events-misc/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.Test1932.run();
+ }
+}
diff --git a/test/1932-monitor-events-misc/src/art/Monitors.java b/test/1932-monitor-events-misc/src/art/Monitors.java
new file mode 100644
index 0000000..f6a99fd
--- /dev/null
+++ b/test/1932-monitor-events-misc/src/art/Monitors.java
@@ -0,0 +1,299 @@
+/*
+ * 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;
+ public NamedLock(String name) {
+ this.name = name;
+ }
+ public String toString() {
+ return String.format("NamedLock[%s]", name);
+ }
+ }
+
+ 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 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 Object 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(Object lock) {
+ this(lock, 10 * 1000);
+ }
+ public LockController(Object 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 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.notify();
+ break;
+ case NOTIFY_ALL:
+ lock.notifyAll();
+ break;
+ case TIMED_WAIT:
+ lock.wait(timeout);
+ break;
+ case WAIT:
+ lock.wait();
+ 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/1932-monitor-events-misc/src/art/Test1932.java b/test/1932-monitor-events-misc/src/art/Test1932.java
new file mode 100644
index 0000000..7f66884
--- /dev/null
+++ b/test/1932-monitor-events-misc/src/art/Test1932.java
@@ -0,0 +1,623 @@
+/*
+ * 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;
+
+public class Test1932 {
+ public static final boolean PRINT_FULL_STACK_TRACE = false;
+ public static final boolean INCLUDE_ANDROID_ONLY_TESTS = false;
+
+ public static interface MonitorHandler {
+ public default void handleMonitorEnter(Thread thd, Object lock) {}
+ public default void handleMonitorEntered(Thread thd, Object lock) {}
+ public default void handleMonitorWait(Thread thd, Object lock, long timeout) {}
+ public default void handleMonitorWaited(Thread thd, Object lock, boolean timed_out) {}
+ }
+
+ public static volatile MonitorHandler HANDLER = null;
+
+ public static void run() throws Exception {
+ Monitors.setupMonitorEvents(
+ Test1932.class,
+ Test1932.class.getDeclaredMethod("handleMonitorEnter", Thread.class, Object.class),
+ Test1932.class.getDeclaredMethod("handleMonitorEntered", Thread.class, Object.class),
+ Test1932.class.getDeclaredMethod("handleMonitorWait",
+ Thread.class, Object.class, Long.TYPE),
+ Test1932.class.getDeclaredMethod("handleMonitorWaited",
+ Thread.class, Object.class, Boolean.TYPE),
+ Monitors.NamedLock.class,
+ null);
+
+ System.out.println("Testing contended locking where lock is released before callback ends.");
+ testLockUncontend(new Monitors.NamedLock("Lock testLockUncontend"));
+
+ System.out.println("Testing throwing exceptions in monitor_enter");
+ testLockThrowEnter(new Monitors.NamedLock("Lock testLockThrowEnter"));
+
+ System.out.println("Testing throwing exceptions in monitor_entered");
+ testLockThrowEntered(new Monitors.NamedLock("Lock testLockThrowEntered"));
+
+ System.out.println("Testing throwing exceptions in both monitorEnter & MonitorEntered");
+ testLockThrowBoth(new Monitors.NamedLock("Lock testLockThrowBoth"));
+
+ // This exposes a difference between the RI and ART. On the RI this test will cause a
+ // JVMTI_EVENT_MONITOR_WAITED event to be sent even though we threw an exception during the
+ // JVMTI_EVENT_MONITOR_WAIT. See b/65558434.
+ System.out.println("Testing throwing exception in MonitorWait event");
+ testThrowWait(new Monitors.NamedLock("Lock testThrowWait"));
+
+ System.out.println("Testing throwing exception in MonitorWait event with illegal aruments");
+ testThrowIllegalWait(new Monitors.NamedLock("Lock testThrowIllegalWait"));
+
+ System.out.println("Testing throwing exception in MonitorWaited event");
+ testThrowWaited(new Monitors.NamedLock("Lock testThrowWaited"));
+
+ System.out.println("Testing throwing exception in MonitorWaited event caused by timeout");
+ testThrowWaitedTimeout(new Monitors.NamedLock("Lock testThrowWaitedTimeout"));
+
+ System.out.println("Testing throwing exception in MonitorWaited event caused by interrupt");
+ testThrowWaitedInterrupt(new Monitors.NamedLock("Lock testThrowWaitedInterrupt"));
+
+ System.out.println("Testing ObjectMonitorInfo inside of events");
+ testMonitorInfoInEvents(new Monitors.NamedLock("Lock testMonitorInfoInEvents"));
+
+ System.out.println("Testing that the monitor can be stolen during the MonitorWaited event.");
+ testWaitEnterInterleaving(new Monitors.NamedLock("test testWaitEnterInterleaving"));
+
+ // TODO We keep this here since it works on android but it's not clear it's behavior we want to
+ // support long term or at all.
+ if (INCLUDE_ANDROID_ONLY_TESTS) {
+ System.out.println(
+ "Testing that the monitor can be still held by notifier during the MonitorWaited " +
+ "event. NB This doesn't work on the RI.");
+ testWaitExitInterleaving(new Monitors.NamedLock("test testWaitExitInterleaving"));
+ }
+
+ System.out.println(
+ "Testing that we can lock and release the monitor in the MonitorWait event");
+ testWaitMonitorEnter(new Monitors.NamedLock("test testWaitMonitorEnter"));
+
+ System.out.println(
+ "Testing that we can lock and release the monitor in the MonitorWaited event");
+ testWaitedMonitorEnter(new Monitors.NamedLock("test testWaitedMonitorEnter"));
+
+ System.out.println("Testing we can perform recursive lock in MonitorEntered");
+ testRecursiveMontiorEnteredLock(new Monitors.NamedLock("test testRecursiveMontiorEnteredLock"));
+
+ System.out.println("Testing the lock state if MonitorEnter throws in a native method");
+ testNativeLockStateThrowEnter(new Monitors.NamedLock("test testNativeLockStateThrowEnter"));
+
+ System.out.println("Testing the lock state if MonitorEntered throws in a native method");
+ testNativeLockStateThrowEntered(new Monitors.NamedLock("test testNativeLockStateThrowEntered"));
+ }
+
+ public static native void doNativeLockPrint(Monitors.NamedLock lk);
+ public static void printLockState(Monitors.NamedLock lk, Object exception, int res) {
+ System.out.println(
+ "MonitorEnter returned: " + res + "\n" +
+ "Lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ printExceptions((Throwable)exception);
+ }
+
+ public static void testNativeLockStateThrowEnter(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEnter(Thread t, Object l) {
+ System.out.println("Unlocking controller1 in MonitorEnter");
+ try {
+ controller1.DoUnlock();
+ } catch (Exception e) {
+ throw new Monitors.TestException("Exception unlocking monitor in MonitorEnter " + l, e);
+ }
+ System.out.println("Throwing exception in MonitorEnter");
+ throw new Monitors.TestException("throwing exception during monitorEnter of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ Thread native_thd = new Thread(() -> {
+ try {
+ doNativeLockPrint(lk);
+ } catch (Throwable e) {
+ System.out.println("Unhandled exception: " + e);
+ e.printStackTrace();
+ }
+ }, "NativeLockStateThrowEnter thread");
+ native_thd.start();
+ native_thd.join();
+ }
+
+ public static void testNativeLockStateThrowEntered(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEnter(Thread t, Object l) {
+ System.out.println("Unlocking controller1 in MonitorEnter");
+ try {
+ controller1.DoUnlock();
+ } catch (Exception e) {
+ throw new Monitors.TestException("Exception unlocking monitor in MonitorEnter " + l, e);
+ }
+ }
+ @Override public void handleMonitorEntered(Thread t, Object l) {
+ System.out.println("Throwing exception in MonitorEntered");
+ throw new Monitors.TestException("throwing exception during monitorEntered of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ Thread native_thd = new Thread(() -> {
+ try {
+ doNativeLockPrint(lk);
+ } catch (Throwable e) {
+ System.out.println("Unhandled exception: " + e);
+ e.printStackTrace();
+ }
+ }, "NativeLockStateThrowEntered thread");
+ native_thd.start();
+ native_thd.join();
+ }
+
+ public static void testRecursiveMontiorEnteredLock(final Monitors.NamedLock lk) throws Exception {
+ Monitors.LockController controller1 = new Monitors.LockController(lk);
+ Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEntered(Thread thd, Object l) {
+ try {
+ System.out.println("In MonitorEntered usage: " + Monitors.getObjectMonitorUsage(lk));
+ synchronized (lk) {
+ System.out.println("In MonitorEntered sync: " + Monitors.getObjectMonitorUsage(lk));
+ }
+ } catch (Exception e) {
+ throw new Monitors.TestException("error while recursive locking!", e);
+ }
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller2.DoLock();
+ controller2.waitForContendedSleep();
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ }
+
+ public static void testWaitedMonitorEnter(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+ try {
+ // make sure that controller2 has acutally unlocked everything, we can be sent earlier
+ // than that on ART.
+ while (controller2.IsLocked()) {}
+ System.out.println("In waited monitor usage: " + Monitors.getObjectMonitorUsage(lk));
+ synchronized (lk) {
+ System.out.println(
+ "In waited monitor usage sync: " + Monitors.getObjectMonitorUsage(lk));
+ }
+ } catch (Exception e) {
+ throw new Monitors.TestException("error while doing unlock in other thread!", e);
+ }
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoNotifyAll();
+ controller2.DoUnlock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ }
+
+ public static void testWaitMonitorEnter(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWait(Thread thd, Object l, long timeout) {
+ try {
+ System.out.println("In wait monitor usage: " + Monitors.getObjectMonitorUsage(lk));
+ synchronized (lk) {
+ System.out.println("In wait monitor usage sync: " + Monitors.getObjectMonitorUsage(lk));
+ }
+ } catch (Exception e) {
+ throw new Monitors.TestException("error while doing unlock in other thread!", e);
+ }
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoNotifyAll();
+ controller2.DoUnlock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ }
+
+ // NB This test cannot be run on the RI. It deadlocks. Leaving for documentation.
+ public static void testWaitExitInterleaving(Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+ System.out.println("un-locking controller1 in controller2 MonitorWaited event");
+ try {
+ controller1.DoUnlock();
+ } catch (Exception e) {
+ throw new Monitors.TestException("error while doing unlock in other thread!", e);
+ }
+ }
+ };
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoWait();
+ controller2.waitForNotifySleep();
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoNotifyAll();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ }
+
+ public static void testWaitEnterInterleaving(Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ final Monitors.LockController controller3 = new Monitors.LockController(lk);
+ final Semaphore unlocked_sem = new Semaphore(0);
+ final Semaphore continue_sem = new Semaphore(0);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+ System.out.println("locking controller3 in controller2 MonitorWaited event");
+ try {
+ unlocked_sem.acquire();
+ controller3.DoLock();
+ controller3.waitForLockToBeHeld();
+ System.out.println(
+ "Controller3 now holds the lock the monitor wait will try to re-acquire");
+ continue_sem.release();
+ } catch (Exception e) {
+ throw new Monitors.TestException("error while doing unlock in other thread!", e);
+ }
+ }
+ };
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoWait();
+ controller2.waitForNotifySleep();
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoNotifyAll();
+ controller1.DoUnlock();
+ // Wait for controller3 to have locked.
+ // We cannot use waitForLockToBeHeld since we could race with the HANDLER waitForLockToBeHeld
+ // function.
+ unlocked_sem.release();
+ continue_sem.acquire();
+ controller3.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ }
+
+ public static void testMonitorInfoInEvents(Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEnter(Thread thd, Object l) {
+ System.out.println("Monitor usage in MonitorEnter: " + Monitors.getObjectMonitorUsage(l));
+ }
+ @Override public void handleMonitorEntered(Thread thd, Object l) {
+ System.out.println("Monitor usage in MonitorEntered: " + Monitors.getObjectMonitorUsage(l));
+ }
+ @Override public void handleMonitorWait(Thread thd, Object l, long timeout) {
+ System.out.println("Monitor usage in MonitorWait: " + Monitors.getObjectMonitorUsage(l));
+ }
+ @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+ // make sure that controller1 has acutally unlocked everything, we can be sent earlier than
+ // that on ART.
+ while (controller1.IsLocked()) {}
+ System.out.println("Monitor usage in MonitorWaited: " + Monitors.getObjectMonitorUsage(l));
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller2.DoLock();
+ controller2.waitForContendedSleep();
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoWait();
+ controller2.waitForNotifySleep();
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoNotifyAll();
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ }
+
+ public static void testThrowWaitedInterrupt(Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+ System.out.println("Throwing exception in MonitorWaited");
+ throw new Monitors.TestException("throwing exception during monitorWaited of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ try {
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+ controller1.interruptWorker();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ System.out.println("No Exception thrown!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller1.DoCleanup();
+ }
+ }
+
+ public static void testThrowWaitedTimeout(Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk, 5 * 1000);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+ System.out.println("Throwing exception in MonitorWaited");
+ throw new Monitors.TestException("throwing exception during monitorWaited of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ try {
+ controller1.DoTimedWait();
+ controller1.waitForNotifySleep();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ System.out.println("No Exception thrown!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller1.DoCleanup();
+ }
+ }
+
+ public static void testThrowWaited(Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+ System.out.println("Throwing exception in MonitorWaited");
+ throw new Monitors.TestException("throwing exception during monitorWaited of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoNotifyAll();
+ controller2.DoUnlock();
+ try {
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ System.out.println("No Exception thrown!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller1.DoCleanup();
+ }
+ }
+
+ public static void testThrowWait(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWait(Thread thd, Object l, long timeout) {
+ System.out.println("Throwing exception in MonitorWait");
+ throw new Monitors.TestException("throwing exception during MonitorWait of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ try {
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ System.out.println("No Exception thrown!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller1.DoCleanup();
+ }
+ }
+
+ public static void testThrowIllegalWait(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk, -100000);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWait(Thread thd, Object l, long timeout) {
+ System.out.println("Throwing exception in MonitorWait timeout = " + timeout);
+ throw new Monitors.TestException("throwing exception during monitorWait of " + l);
+ }
+ };
+ try {
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoTimedWait();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ System.out.println("No Exception thrown!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller1.DoCleanup();
+ }
+ }
+
+ public static void testLockUncontend(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEnter(Thread thd, Object lock) {
+ if (controller1.IsLocked()) {
+ System.out.println("Releasing " + lk + " during monitorEnter event.");
+ try {
+ controller1.DoUnlock();
+ } catch (Exception e) {
+ throw new Error("Unable to unlock controller1", e);
+ }
+ } else {
+ throw new Error("controller1 does not seem to hold the lock!");
+ }
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ // This will call handleMonitorEnter but will release during the callback.
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ if (controller1.IsLocked()) {
+ throw new Error("controller1 still holds the lock somehow!");
+ }
+ controller2.DoUnlock();
+ }
+
+ public static void testLockThrowEnter(Monitors.NamedLock lk) throws Exception {
+ Monitors.LockController controller1 = new Monitors.LockController(lk);
+ Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEnter(Thread t, Object l) {
+ System.out.println("Throwing exception in MonitorEnter");
+ throw new Monitors.TestException("throwing exception during monitorEnter of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ try {
+ controller2.DoLock();
+ controller2.waitForContendedSleep();
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ System.out.println("Did not get an exception!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller2.DoCleanup();
+ }
+ }
+
+ public static void testLockThrowEntered(Monitors.NamedLock lk) throws Exception {
+ Monitors.LockController controller1 = new Monitors.LockController(lk);
+ Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEntered(Thread t, Object l) {
+ System.out.println("Throwing exception in MonitorEntered");
+ throw new Monitors.TestException("throwing exception during monitorEntered of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ try {
+ controller2.DoLock();
+ controller2.waitForContendedSleep();
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ System.out.println("Did not get an exception!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller2.DoCleanup();
+ }
+ }
+
+ public static void testLockThrowBoth(Monitors.NamedLock lk) throws Exception {
+ Monitors.LockController controller1 = new Monitors.LockController(lk);
+ Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEnter(Thread t, Object l) {
+ System.out.println("Throwing exception in MonitorEnter");
+ throw new Monitors.TestException("throwing exception during monitorEnter of " + l);
+ }
+ @Override public void handleMonitorEntered(Thread t, Object l) {
+ System.out.println("Throwing exception in MonitorEntered");
+ throw new Monitors.TestException("throwing exception during monitorEntered of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ try {
+ controller2.DoLock();
+ controller2.waitForContendedSleep();
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ System.out.println("Did not get an exception!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller2.DoCleanup();
+ }
+ }
+
+ public static void printExceptions(Throwable t) {
+ System.out.println("Caught exception: " + t);
+ for (Throwable c = t.getCause(); c != null; c = c.getCause()) {
+ System.out.println("\tCaused by: " +
+ (Test1932.class.getPackage().equals(c.getClass().getPackage())
+ ? c.toString() : c.getClass().toString()));
+ }
+ if (PRINT_FULL_STACK_TRACE) {
+ t.printStackTrace();
+ }
+ }
+
+ public static void handleMonitorEnter(Thread thd, Object lock) {
+ System.out.println(thd.getName() + " contended-LOCKING " + lock);
+ if (HANDLER != null) {
+ HANDLER.handleMonitorEnter(thd, lock);
+ }
+ }
+
+ public static void handleMonitorEntered(Thread thd, Object lock) {
+ System.out.println(thd.getName() + " LOCKED " + lock);
+ if (HANDLER != null) {
+ HANDLER.handleMonitorEntered(thd, lock);
+ }
+ }
+ public static void handleMonitorWait(Thread thd, Object lock, long timeout) {
+ System.out.println(thd.getName() + " start-monitor-wait " + lock + " timeout: " + timeout);
+ if (HANDLER != null) {
+ HANDLER.handleMonitorWait(thd, lock, timeout);
+ }
+ }
+
+ public static void handleMonitorWaited(Thread thd, Object lock, boolean timed_out) {
+ System.out.println(thd.getName() + " monitor-waited " + lock + " timed_out: " + timed_out);
+ if (HANDLER != null) {
+ HANDLER.handleMonitorWaited(thd, lock, timed_out);
+ }
+ }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 2f23056..d56c0b5 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -302,6 +302,7 @@
"1926-missed-frame-pop/frame_pop_missed.cc",
"1927-exception-event/exception_event.cc",
"1930-monitor-info/monitor.cc",
+ "1932-monitor-events-misc/monitor_misc.cc"
],
shared_libs: [
"libbase",
diff --git a/test/run-test b/test/run-test
index 9996986..79f3d1e 100755
--- a/test/run-test
+++ b/test/run-test
@@ -810,6 +810,7 @@
good="no"
good_build="yes"
good_run="yes"
+export TEST_RUNTIME="${runtime}"
if [ "$dev_mode" = "yes" ]; then
"./${build}" $build_args 2>&1
build_exit="$?"
diff --git a/test/ti-agent/monitors_helper.cc b/test/ti-agent/monitors_helper.cc
index 7c28ede..97d8427 100644
--- a/test/ti-agent/monitors_helper.cc
+++ b/test/ti-agent/monitors_helper.cc
@@ -13,13 +13,17 @@
* 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 "jni_helper.h"
#include "test_env.h"
#include "scoped_local_ref.h"
+
namespace art {
namespace common_monitors {
@@ -58,5 +62,154 @@
obj, usage.owner, usage.entry_count, wait, notify_wait);
}
+struct MonitorsData {
+ jclass test_klass;
+ jmethodID monitor_enter;
+ jmethodID monitor_entered;
+ jmethodID monitor_wait;
+ jmethodID monitor_waited;
+ jclass monitor_klass;
+};
+
+static void monitorEnterCB(jvmtiEnv* jvmti,
+ JNIEnv* jnienv,
+ jthread thr,
+ jobject obj) {
+ MonitorsData* data = nullptr;
+ if (JvmtiErrorToException(jnienv, jvmti,
+ jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+ return;
+ }
+ if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) {
+ return;
+ }
+ jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_enter, thr, obj);
+}
+static void monitorEnteredCB(jvmtiEnv* jvmti,
+ JNIEnv* jnienv,
+ jthread thr,
+ jobject obj) {
+ MonitorsData* data = nullptr;
+ if (JvmtiErrorToException(jnienv, jvmti,
+ jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+ return;
+ }
+ if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) {
+ return;
+ }
+ jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_entered, thr, obj);
+}
+static void monitorWaitCB(jvmtiEnv* jvmti,
+ JNIEnv* jnienv,
+ jthread thr,
+ jobject obj,
+ jlong timeout) {
+ MonitorsData* data = nullptr;
+ if (JvmtiErrorToException(jnienv, jvmti,
+ jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+ return;
+ }
+ if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) {
+ return;
+ }
+ jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_wait, thr, obj, timeout);
+}
+static void monitorWaitedCB(jvmtiEnv* jvmti,
+ JNIEnv* jnienv,
+ jthread thr,
+ jobject obj,
+ jboolean timed_out) {
+ MonitorsData* data = nullptr;
+ if (JvmtiErrorToException(jnienv, jvmti,
+ jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+ return;
+ }
+ if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) {
+ return;
+ }
+ jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_waited, thr, obj, timed_out);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Monitors_setupMonitorEvents(
+ JNIEnv* env,
+ jclass,
+ jclass test_klass,
+ jobject monitor_enter,
+ jobject monitor_entered,
+ jobject monitor_wait,
+ jobject monitor_waited,
+ jclass monitor_klass,
+ jthread thr) {
+ MonitorsData* data = nullptr;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->Allocate(sizeof(MonitorsData),
+ reinterpret_cast<unsigned char**>(&data)))) {
+ return;
+ }
+ jvmtiCapabilities caps;
+ memset(&caps, 0, sizeof(caps));
+ caps.can_generate_monitor_events = 1;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps))) {
+ return;
+ }
+
+ memset(data, 0, sizeof(MonitorsData));
+ data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(test_klass));
+ data->monitor_enter = env->FromReflectedMethod(monitor_enter);
+ data->monitor_entered = env->FromReflectedMethod(monitor_entered);
+ data->monitor_wait = env->FromReflectedMethod(monitor_wait);
+ data->monitor_waited = env->FromReflectedMethod(monitor_waited);
+ data->monitor_klass = reinterpret_cast<jclass>(env->NewGlobalRef(monitor_klass));
+ MonitorsData* old_data = nullptr;
+ if (JvmtiErrorToException(env, jvmti_env,
+ jvmti_env->GetEnvironmentLocalStorage(
+ reinterpret_cast<void**>(&old_data)))) {
+ return;
+ } else if (old_data != nullptr && old_data->test_klass != nullptr) {
+ ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+ env->ThrowNew(rt_exception.get(), "Environment already has local storage set!");
+ return;
+ }
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) {
+ return;
+ }
+
+ jvmtiEventCallbacks cb;
+ memset(&cb, 0, sizeof(cb));
+ cb.MonitorContendedEnter = monitorEnterCB;
+ cb.MonitorContendedEntered = monitorEnteredCB;
+ cb.MonitorWait = monitorWaitCB;
+ cb.MonitorWaited = monitorWaitedCB;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+ return;
+ }
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(
+ JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTER, thr))) {
+ return;
+ }
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(
+ JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, thr))) {
+ return;
+ }
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(
+ JVMTI_ENABLE, JVMTI_EVENT_MONITOR_WAIT, thr))) {
+ return;
+ }
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(
+ JVMTI_ENABLE, JVMTI_EVENT_MONITOR_WAITED, thr))) {
+ return;
+ }
+}
+
} // namespace common_monitors
} // namespace art
+