diff options
| author | 2017-09-05 14:51:49 -0700 | |
|---|---|---|
| committer | 2017-09-14 09:57:03 -0700 | |
| commit | 77fee87b262e969b29a9ac121a8bcbf87b68d9ce (patch) | |
| tree | 3280ba8d887045217bfbcb81eb624f571eeee7d0 | |
| parent | ec995142998f6c7371734e6df95b5e2c80b18d27 (diff) | |
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
36 files changed, 2541 insertions, 60 deletions
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h index d3f52f638d..8e3cf21f19 100644 --- a/openjdkjvmti/art_jvmti.h +++ b/openjdkjvmti/art_jvmti.h @@ -247,7 +247,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .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 c41e15eaa8..5911a0373d 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 @@ static void SetupObjectAllocationTracking(art::gc::AllocationListener* listener, } } +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 @@ static void SetupGcPauseTracking(JvmtiGcPauseListener* listener, ArtJvmtiEvent e } } -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 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat 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 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat 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 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat // the union. val.j = return_value.GetJ(); RunEventCallback<ArtJvmtiEvent::kMethodExit>( + event_handler_, self, jnienv, art::jni::EncodeArtMethod(method), @@ -412,6 +516,7 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat CHECK(!old_exception.IsNull()); self->ClearException(); RunEventCallback<ArtJvmtiEvent::kMethodExit>( + event_handler_, self, jnienv, art::jni::EncodeArtMethod(method), @@ -442,11 +547,11 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat 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 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat 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 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat jvalue val; val.l = fval.get(); RunEventCallback<ArtJvmtiEvent::kFieldModification>( + event_handler_, self, jnienv, art::jni::EncodeArtMethod(method), @@ -525,6 +632,7 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat // the union. val.j = field_value.GetJ(); RunEventCallback<ArtJvmtiEvent::kFieldModification>( + event_handler_, self, jnienv, art::jni::EncodeArtMethod(method), @@ -545,6 +653,7 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat 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 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat 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 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat 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 @@ void EventHandler::HandleLocalAccessCapabilityAdded() { 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 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { 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 @@ EventHandler::EventHandler() { 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 b49e80c46d..0d36c46fa1 100644 --- a/openjdkjvmti/events.h +++ b/openjdkjvmti/events.h @@ -30,6 +30,7 @@ struct ArtJvmTiEnv; 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 @@ class EventHandler { 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 @@ class EventHandler { 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 27d01ea17d..d437e52d0f 100644 --- a/openjdkjvmti/ti_thread.cc +++ b/openjdkjvmti/ti_thread.cc @@ -392,6 +392,8 @@ static jint GetJavaStateFromInternal(const InternalThreadState& state) { 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 af56810fcb..2868180a3e 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -2212,6 +2212,8 @@ JDWP::JdwpThreadStatus Dbg::ToJdwpThreadStatus(ThreadState state) { 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 b4f945a5d6..4bcce217d6 100644 --- a/runtime/entrypoints/quick/quick_lock_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_lock_entrypoints.cc @@ -29,15 +29,22 @@ extern "C" int artLockObjectFromCode(mirror::Object* obj, Thread* self) 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 0704a68d95..e928644054 100644 --- a/runtime/gc/task_processor.cc +++ b/runtime/gc/task_processor.cc @@ -34,14 +34,14 @@ TaskProcessor::~TaskProcessor() { } 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 3ccab853c3..50bd7e73cd 100644 --- a/runtime/interpreter/interpreter_common.h +++ b/runtime/interpreter/interpreter_common.h @@ -65,9 +65,16 @@ template <bool kMonitorCounting> 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 f96792dcb7..d74cec325a 100644 --- a/runtime/jni_internal.cc +++ b/runtime/jni_internal.cc @@ -2398,10 +2398,12 @@ class JNI { 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 @@ class JNI { 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 80e6ad3d47..051e015d80 100644 --- a/runtime/monitor.cc +++ b/runtime/monitor.cc @@ -346,11 +346,31 @@ bool Monitor::TryLock(Thread* self) { 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 @@ void Monitor::Lock(Thread* self) { } 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 @@ void Monitor::Lock(Thread* self) { << 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 @@ void Monitor::Lock(Thread* self) { 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 @@ void Monitor::Wait(Thread* self, int64_t ms, int32_t ns, 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 @@ void Monitor::Wait(Thread* self, int64_t ms, int32_t ns, 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 @@ void Monitor::Wait(Thread* self, int64_t ms, int32_t ns, 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 @@ void Monitor::InflateThinLocked(Thread* self, Handle<mirror::Object> obj, LockWo 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 @@ mirror::Object* Monitor::MonitorEnter(Thread* self, mirror::Object* obj, bool tr 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 @@ void Monitor::Wait(Thread* self, mirror::Object *obj, int64_t ms, int32_t ns, 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 @@ void Monitor::Wait(Thread* self, mirror::Object *obj, int64_t ms, int32_t ns, } 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 @@ void Monitor::DescribeWait(std::ostream& os, const Thread* thread) { 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 09faeba9a6..d7aef34e0b 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 @@ namespace mirror { 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 @@ class Monitor { 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 4fbbb72631..94007ffa1e 100644 --- a/runtime/native/java_lang_Thread.cc +++ b/runtime/native/java_lang_Thread.cc @@ -86,6 +86,8 @@ static jint Thread_nativeGetStatus(JNIEnv* env, jobject java_thread, jboolean ha 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 4e84468744..2eb4411e0b 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -658,6 +658,10 @@ class Runtime { 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 16d6c13722..88d3f28583 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 @@ static inline void Remove(T* cb, std::vector<T*>* data) { } } +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 e8f1824262..fa686d30e1 100644 --- a/runtime/runtime_callbacks.h +++ b/runtime/runtime_callbacks.h @@ -29,12 +29,14 @@ namespace art { 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 @@ class RuntimePhaseCallback { 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 @@ class RuntimeCallbacks { /*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 @@ class RuntimeCallbacks { 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 ac2ed9e295..ef172586cf 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 @@ TEST_F(RuntimePhaseCallbackRuntimeCallbacksTest, Phases) { 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 8f2f70f46e..8edfeecbdd 100644 --- a/runtime/thread_state.h +++ b/runtime/thread_state.h @@ -29,6 +29,8 @@ enum ThreadState { 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 26f7718dc5..f6a99fdae0 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 class Monitors { } 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 0000000000..33a9bd3684 --- /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 0000000000..ae07c53c2a --- /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 0000000000..e92b873956 --- /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 0000000000..81c9d2c568 --- /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 0000000000..f6a99fdae0 --- /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 0000000000..ccefede9f8 --- /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 0000000000..8a84388a8f --- /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 0000000000..b33aa7d3a3 --- /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 0000000000..674ef56c70 --- /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 0000000000..f6b2285a3f --- /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 0000000000..842c550c7c --- /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 0000000000..e92b873956 --- /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 0000000000..0100074bfe --- /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 0000000000..f6a99fdae0 --- /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 0000000000..7f66884ce4 --- /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 2f2305607e..d56c0b50c2 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -302,6 +302,7 @@ art_cc_defaults { "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 9996986a92..79f3d1e256 100755 --- a/test/run-test +++ b/test/run-test @@ -810,6 +810,7 @@ fi 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 7c28edecd2..97d8427573 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 @@ extern "C" JNIEXPORT jobject JNICALL Java_art_Monitors_getObjectMonitorUsage( 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 + |