Add support for JVMTI monitor events.
Adds support for the JVMTI can_generate_monitor_events capability and
all associated events. This adds support for the
JVMTI_EVENT_MONITOR_WAIT, JVMTI_EVENT_MONITOR_WAITED,
JVMTI_EVENT_MONITOR_CONTENDED_ENTER, and
JVMTI_EVENT_MONITOR_CONTENDED_ENTERED events.
Bug: 65558434
Bug: 62821960
Bug: 34415266
Test: ./test.py --host -j50
Change-Id: I0fe8038e6c4249e77d37a67e5056b5d2a94b6f48
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index af56810..2868180 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -2212,6 +2212,8 @@
case kTerminated:
return JDWP::TS_ZOMBIE;
case kTimedWaiting:
+ case kWaitingForTaskProcessor:
+ case kWaitingForLockInflation:
case kWaitingForCheckPointsToRun:
case kWaitingForDebuggerSend:
case kWaitingForDebuggerSuspension:
diff --git a/runtime/entrypoints/quick/quick_lock_entrypoints.cc b/runtime/entrypoints/quick/quick_lock_entrypoints.cc
index b4f945a..4bcce21 100644
--- a/runtime/entrypoints/quick/quick_lock_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_lock_entrypoints.cc
@@ -29,15 +29,22 @@
ThrowNullPointerException("Null reference used for synchronization (monitor-enter)");
return -1; // Failure.
} else {
- if (kIsDebugBuild) {
- obj = obj->MonitorEnter(self); // May block
- CHECK(self->HoldsLock(obj));
- CHECK(!self->IsExceptionPending());
+ obj = obj->MonitorEnter(self); // May block
+ DCHECK(self->HoldsLock(obj));
+ // Exceptions can be thrown by monitor event listeners. This is expected to be rare however.
+ if (UNLIKELY(self->IsExceptionPending())) {
+ // TODO Remove this DCHECK if we expand the use of monitor callbacks.
+ DCHECK(Runtime::Current()->HasLoadedPlugins())
+ << "Exceptions are only expected to be thrown by plugin code which doesn't seem to be "
+ << "loaded.";
+ // We need to get rid of the lock
+ bool unlocked = obj->MonitorExit(self);
+ DCHECK(unlocked);
+ return -1; // Failure.
} else {
- obj->MonitorEnter(self); // May block
+ DCHECK(self->HoldsLock(obj));
+ return 0; // Success.
}
- return 0; // Success.
- // Only possible exception is NPE and is handled before entry
}
}
diff --git a/runtime/gc/task_processor.cc b/runtime/gc/task_processor.cc
index 0704a68..e928644 100644
--- a/runtime/gc/task_processor.cc
+++ b/runtime/gc/task_processor.cc
@@ -34,14 +34,14 @@
}
void TaskProcessor::AddTask(Thread* self, HeapTask* task) {
- ScopedThreadStateChange tsc(self, kBlocked);
+ ScopedThreadStateChange tsc(self, kWaitingForTaskProcessor);
MutexLock mu(self, *lock_);
tasks_.insert(task);
cond_->Signal(self);
}
HeapTask* TaskProcessor::GetTask(Thread* self) {
- ScopedThreadStateChange tsc(self, kBlocked);
+ ScopedThreadStateChange tsc(self, kWaitingForTaskProcessor);
MutexLock mu(self, *lock_);
while (true) {
if (tasks_.empty()) {
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index 3ccab85..50bd7e7 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -65,9 +65,16 @@
static inline void DoMonitorEnter(Thread* self, ShadowFrame* frame, ObjPtr<mirror::Object> ref)
NO_THREAD_SAFETY_ANALYSIS
REQUIRES(!Roles::uninterruptible_) {
+ DCHECK(!ref.IsNull());
StackHandleScope<1> hs(self);
Handle<mirror::Object> h_ref(hs.NewHandle(ref));
h_ref->MonitorEnter(self);
+ DCHECK(self->HoldsLock(h_ref.Get()));
+ if (UNLIKELY(self->IsExceptionPending())) {
+ bool unlocked = h_ref->MonitorExit(self);
+ DCHECK(unlocked);
+ return;
+ }
if (kMonitorCounting && frame->GetMethod()->MustCountLocks()) {
frame->GetLockCountData().AddMonitor(self, h_ref.Get());
}
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index f96792d..d74cec3 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -2398,10 +2398,12 @@
ScopedObjectAccess soa(env);
ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(java_object);
o = o->MonitorEnter(soa.Self());
+ if (soa.Self()->HoldsLock(o)) {
+ soa.Env()->monitors.Add(o);
+ }
if (soa.Self()->IsExceptionPending()) {
return JNI_ERR;
}
- soa.Env()->monitors.Add(o);
return JNI_OK;
}
@@ -2409,11 +2411,14 @@
CHECK_NON_NULL_ARGUMENT_RETURN(java_object, JNI_ERR);
ScopedObjectAccess soa(env);
ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(java_object);
+ bool remove_mon = soa.Self()->HoldsLock(o);
o->MonitorExit(soa.Self());
+ if (remove_mon) {
+ soa.Env()->monitors.Remove(o);
+ }
if (soa.Self()->IsExceptionPending()) {
return JNI_ERR;
}
- soa.Env()->monitors.Remove(o);
return JNI_OK;
}
diff --git a/runtime/monitor.cc b/runtime/monitor.cc
index 80e6ad3..051e015 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -346,11 +346,31 @@
return TryLockLocked(self);
}
+// Asserts that a mutex isn't held when the class comes into and out of scope.
+class ScopedAssertNotHeld {
+ public:
+ ScopedAssertNotHeld(Thread* self, Mutex& mu) : self_(self), mu_(mu) {
+ mu_.AssertNotHeld(self_);
+ }
+
+ ~ScopedAssertNotHeld() {
+ mu_.AssertNotHeld(self_);
+ }
+
+ private:
+ Thread* const self_;
+ Mutex& mu_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedAssertNotHeld);
+};
+
+template <LockReason reason>
void Monitor::Lock(Thread* self) {
- MutexLock mu(self, monitor_lock_);
+ ScopedAssertNotHeld sanh(self, monitor_lock_);
+ bool called_monitors_callback = false;
+ monitor_lock_.Lock(self);
while (true) {
if (TryLockLocked(self)) {
- return;
+ break;
}
// Contended.
const bool log_contention = (lock_profiling_threshold_ != 0);
@@ -389,6 +409,12 @@
}
monitor_lock_.Unlock(self); // Let go of locks in order.
+ // Call the contended locking cb once and only once. Also only call it if we are locking for
+ // the first time, not during a Wait wakeup.
+ if (reason == LockReason::kForLock && !called_monitors_callback) {
+ called_monitors_callback = true;
+ Runtime::Current()->GetRuntimeCallbacks()->MonitorContendedLocking(this);
+ }
self->SetMonitorEnterObject(GetObject());
{
ScopedThreadSuspension tsc(self, kBlocked); // Change to blocked and give up mutator_lock_.
@@ -492,10 +518,10 @@
<< PrettyDuration(MsToNs(wait_ms));
}
LogContentionEvent(self,
- wait_ms,
- sample_percent,
- owners_method,
- owners_dex_pc);
+ wait_ms,
+ sample_percent,
+ owners_method,
+ owners_dex_pc);
}
}
}
@@ -508,8 +534,18 @@
monitor_lock_.Lock(self); // Reacquire locks in order.
--num_waiters_;
}
+ monitor_lock_.Unlock(self);
+ // We need to pair this with a single contended locking call. NB we match the RI behavior and call
+ // this even if MonitorEnter failed.
+ if (called_monitors_callback) {
+ CHECK(reason == LockReason::kForLock);
+ Runtime::Current()->GetRuntimeCallbacks()->MonitorContendedLocked(this);
+ }
}
+template void Monitor::Lock<LockReason::kForLock>(Thread* self);
+template void Monitor::Lock<LockReason::kForWait>(Thread* self);
+
static void ThrowIllegalMonitorStateExceptionF(const char* fmt, ...)
__attribute__((format(printf, 1, 2)));
@@ -690,6 +726,7 @@
AtraceMonitorLock(self, GetObject(), true /* is_wait */);
bool was_interrupted = false;
+ bool timed_out = false;
{
// Update thread state. If the GC wakes up, it'll ignore us, knowing
// that we won't touch any references in this state, and we'll check
@@ -718,7 +755,7 @@
self->GetWaitConditionVariable()->Wait(self);
} else {
DCHECK(why == kTimedWaiting || why == kSleeping) << why;
- self->GetWaitConditionVariable()->TimedWait(self, ms, ns);
+ timed_out = self->GetWaitConditionVariable()->TimedWait(self, ms, ns);
}
was_interrupted = self->IsInterrupted();
}
@@ -751,8 +788,11 @@
AtraceMonitorUnlock(); // End Wait().
+ // We just slept, tell the runtime callbacks about this.
+ Runtime::Current()->GetRuntimeCallbacks()->MonitorWaitFinished(this, timed_out);
+
// Re-acquire the monitor and lock.
- Lock(self);
+ Lock<LockReason::kForWait>(self);
monitor_lock_.Lock(self);
self->GetWaitMutex()->AssertNotHeld(self);
@@ -897,7 +937,7 @@
bool timed_out;
Thread* owner;
{
- ScopedThreadSuspension sts(self, kBlocked);
+ ScopedThreadSuspension sts(self, kWaitingForLockInflation);
owner = thread_list->SuspendThreadByThreadId(owner_thread_id,
SuspendReason::kInternal,
&timed_out);
@@ -989,10 +1029,10 @@
contention_count++;
Runtime* runtime = Runtime::Current();
if (contention_count <= runtime->GetMaxSpinsBeforeThinLockInflation()) {
- // TODO: Consider switching the thread state to kBlocked when we are yielding.
- // Use sched_yield instead of NanoSleep since NanoSleep can wait much longer than the
- // parameter you pass in. This can cause thread suspension to take excessively long
- // and make long pauses. See b/16307460.
+ // TODO: Consider switching the thread state to kWaitingForLockInflation when we are
+ // yielding. Use sched_yield instead of NanoSleep since NanoSleep can wait much longer
+ // than the parameter you pass in. This can cause thread suspension to take excessively
+ // long and make long pauses. See b/16307460.
// TODO: We should literally spin first, without sched_yield. Sched_yield either does
// nothing (at significant expense), or guarantees that we wait at least microseconds.
// If the owner is running, I would expect the median lock hold time to be hundreds
@@ -1098,7 +1138,16 @@
bool interruptShouldThrow, ThreadState why) {
DCHECK(self != nullptr);
DCHECK(obj != nullptr);
- LockWord lock_word = obj->GetLockWord(true);
+ StackHandleScope<1> hs(self);
+ Handle<mirror::Object> h_obj(hs.NewHandle(obj));
+
+ Runtime::Current()->GetRuntimeCallbacks()->ObjectWaitStart(h_obj, ms);
+ if (UNLIKELY(self->IsExceptionPending())) {
+ // See b/65558434 for information on handling of exceptions here.
+ return;
+ }
+
+ LockWord lock_word = h_obj->GetLockWord(true);
while (lock_word.GetState() != LockWord::kFatLocked) {
switch (lock_word.GetState()) {
case LockWord::kHashCode:
@@ -1115,8 +1164,8 @@
} else {
// We own the lock, inflate to enqueue ourself on the Monitor. May fail spuriously so
// re-load.
- Inflate(self, self, obj, 0);
- lock_word = obj->GetLockWord(true);
+ Inflate(self, self, h_obj.Get(), 0);
+ lock_word = h_obj->GetLockWord(true);
}
break;
}
@@ -1203,8 +1252,9 @@
if (monitor != nullptr) {
pretty_object = monitor->GetObject();
}
- } else if (state == kBlocked) {
- wait_message = " - waiting to lock ";
+ } else if (state == kBlocked || state == kWaitingForLockInflation) {
+ wait_message = (state == kBlocked) ? " - waiting to lock "
+ : " - waiting for lock inflation of ";
pretty_object = thread->GetMonitorEnterObject();
if (pretty_object != nullptr) {
if (kUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
diff --git a/runtime/monitor.h b/runtime/monitor.h
index 09faeba..d7aef34 100644
--- a/runtime/monitor.h
+++ b/runtime/monitor.h
@@ -31,6 +31,7 @@
#include "gc_root.h"
#include "lock_word.h"
#include "read_barrier_option.h"
+#include "runtime_callbacks.h"
#include "thread_state.h"
namespace art {
@@ -47,6 +48,11 @@
class Object;
} // namespace mirror
+enum class LockReason {
+ kForWait,
+ kForLock,
+};
+
class Monitor {
public:
// The default number of spins that are done before thread suspension is used to forcibly inflate
@@ -205,9 +211,11 @@
REQUIRES(monitor_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
+ template<LockReason reason = LockReason::kForLock>
void Lock(Thread* self)
REQUIRES(!monitor_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
+
bool Unlock(Thread* thread)
REQUIRES(!monitor_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc
index 4fbbb72..94007ff 100644
--- a/runtime/native/java_lang_Thread.cc
+++ b/runtime/native/java_lang_Thread.cc
@@ -86,6 +86,8 @@
case kWaiting: return kJavaWaiting;
case kStarting: return kJavaNew;
case kNative: return kJavaRunnable;
+ case kWaitingForTaskProcessor: return kJavaWaiting;
+ case kWaitingForLockInflation: return kJavaWaiting;
case kWaitingForGcToComplete: return kJavaWaiting;
case kWaitingPerformingGc: return kJavaWaiting;
case kWaitingForCheckPointsToRun: return kJavaWaiting;
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 4e84468..2eb4411 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -658,6 +658,10 @@
RuntimeCallbacks* GetRuntimeCallbacks();
+ bool HasLoadedPlugins() const {
+ return !plugins_.empty();
+ }
+
void InitThreadGroups(Thread* self);
void SetDumpGCPerformanceOnShutdown(bool value) {
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index 16d6c13..88d3f28 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -21,6 +21,7 @@
#include "art_method.h"
#include "base/macros.h"
#include "class_linker.h"
+#include "monitor.h"
#include "thread.h"
namespace art {
@@ -38,6 +39,38 @@
}
}
+void RuntimeCallbacks::MonitorContendedLocking(Monitor* m) {
+ for (MonitorCallback* cb : monitor_callbacks_) {
+ cb->MonitorContendedLocking(m);
+ }
+}
+
+void RuntimeCallbacks::MonitorContendedLocked(Monitor* m) {
+ for (MonitorCallback* cb : monitor_callbacks_) {
+ cb->MonitorContendedLocked(m);
+ }
+}
+
+void RuntimeCallbacks::ObjectWaitStart(Handle<mirror::Object> m, int64_t timeout) {
+ for (MonitorCallback* cb : monitor_callbacks_) {
+ cb->ObjectWaitStart(m, timeout);
+ }
+}
+
+void RuntimeCallbacks::MonitorWaitFinished(Monitor* m, bool timeout) {
+ for (MonitorCallback* cb : monitor_callbacks_) {
+ cb->MonitorWaitFinished(m, timeout);
+ }
+}
+
+void RuntimeCallbacks::AddMonitorCallback(MonitorCallback* cb) {
+ monitor_callbacks_.push_back(cb);
+}
+
+void RuntimeCallbacks::RemoveMonitorCallback(MonitorCallback* cb) {
+ Remove(cb, &monitor_callbacks_);
+}
+
void RuntimeCallbacks::RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) {
Remove(cb, &thread_callbacks_);
}
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index e8f1824..fa686d3 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -29,12 +29,14 @@
namespace mirror {
class Class;
class ClassLoader;
+class Object;
} // namespace mirror
class ArtMethod;
class ClassLoadCallback;
class Thread;
class MethodCallback;
+class Monitor;
class ThreadLifecycleCallback;
// Note: RuntimeCallbacks uses the mutator lock to synchronize the callback lists. A thread must
@@ -73,6 +75,25 @@
virtual void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
};
+class MonitorCallback {
+ public:
+ // Called just before the thread goes to sleep to wait for the monitor to become unlocked.
+ virtual void MonitorContendedLocking(Monitor* mon) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+ // Called just after the monitor has been successfully acquired when it was already locked.
+ virtual void MonitorContendedLocked(Monitor* mon) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+ // Called on entry to the Object#wait method regardless of whether or not the call is valid.
+ virtual void ObjectWaitStart(Handle<mirror::Object> obj, int64_t millis_timeout)
+ REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+
+ // Called just after the monitor has woken up from going to sleep for a wait(). At this point the
+ // thread does not possess a lock on the monitor. This will only be called for threads wait calls
+ // where the thread did (or at least could have) gone to sleep.
+ virtual void MonitorWaitFinished(Monitor* m, bool timed_out)
+ REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+
+ virtual ~MonitorCallback() {}
+};
+
class RuntimeCallbacks {
public:
void AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) REQUIRES(Locks::mutator_lock_);
@@ -120,6 +141,16 @@
/*out*/void** new_implementation)
REQUIRES_SHARED(Locks::mutator_lock_);
+ void MonitorContendedLocking(Monitor* m) REQUIRES_SHARED(Locks::mutator_lock_);
+ void MonitorContendedLocked(Monitor* m) REQUIRES_SHARED(Locks::mutator_lock_);
+ void ObjectWaitStart(Handle<mirror::Object> m, int64_t timeout)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ void MonitorWaitFinished(Monitor* m, bool timed_out)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ void AddMonitorCallback(MonitorCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
+ void RemoveMonitorCallback(MonitorCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
+
private:
std::vector<ThreadLifecycleCallback*> thread_callbacks_
GUARDED_BY(Locks::mutator_lock_);
@@ -131,6 +162,8 @@
GUARDED_BY(Locks::mutator_lock_);
std::vector<MethodCallback*> method_callbacks_
GUARDED_BY(Locks::mutator_lock_);
+ std::vector<MonitorCallback*> monitor_callbacks_
+ GUARDED_BY(Locks::mutator_lock_);
};
} // namespace art
diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc
index ac2ed9e..ef17258 100644
--- a/runtime/runtime_callbacks_test.cc
+++ b/runtime/runtime_callbacks_test.cc
@@ -22,6 +22,7 @@
#include <initializer_list>
#include <memory>
+#include <mutex>
#include <string>
#include "jni.h"
@@ -29,12 +30,14 @@
#include "art_method-inl.h"
#include "base/mutex.h"
#include "class_linker.h"
+#include "class_reference.h"
#include "common_runtime_test.h"
#include "handle.h"
#include "handle_scope-inl.h"
#include "mem_map.h"
#include "mirror/class-inl.h"
#include "mirror/class_loader.h"
+#include "monitor.h"
#include "nativehelper/ScopedLocalRef.h"
#include "obj_ptr.h"
#include "runtime.h"
@@ -433,4 +436,87 @@
ASSERT_EQ(1u, cb_.death_seen);
}
+class MonitorWaitCallbacksTest : public RuntimeCallbacksTest {
+ protected:
+ void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+ Runtime::Current()->GetRuntimeCallbacks()->AddMonitorCallback(&cb_);
+ }
+ void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+ Runtime::Current()->GetRuntimeCallbacks()->RemoveMonitorCallback(&cb_);
+ }
+
+ struct Callback : public MonitorCallback {
+ bool IsInterestingObject(mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (!obj->IsClass()) {
+ return false;
+ }
+ std::lock_guard<std::mutex> lock(ref_guard_);
+ mirror::Class* k = obj->AsClass();
+ ClassReference test = { &k->GetDexFile(), k->GetDexClassDefIndex() };
+ return ref_ == test;
+ }
+
+ void SetInterestingObject(mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ std::lock_guard<std::mutex> lock(ref_guard_);
+ mirror::Class* k = obj->AsClass();
+ ref_ = { &k->GetDexFile(), k->GetDexClassDefIndex() };
+ }
+
+ void MonitorContendedLocking(Monitor* mon ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) { }
+
+ void MonitorContendedLocked(Monitor* mon ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) { }
+
+ void ObjectWaitStart(Handle<mirror::Object> obj, int64_t millis ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (IsInterestingObject(obj.Get())) {
+ saw_wait_start_ = true;
+ }
+ }
+
+ void MonitorWaitFinished(Monitor* m, bool timed_out ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (IsInterestingObject(m->GetObject())) {
+ saw_wait_finished_ = true;
+ }
+ }
+
+ std::mutex ref_guard_;
+ ClassReference ref_ = {nullptr, 0};
+ bool saw_wait_start_ = false;
+ bool saw_wait_finished_ = false;
+ };
+
+ Callback cb_;
+};
+
+// TODO It would be good to have more tests for this but due to the multi-threaded nature of the
+// callbacks this is difficult. For now the run-tests 1931 & 1932 should be sufficient.
+TEST_F(MonitorWaitCallbacksTest, WaitUnlocked) {
+ ASSERT_FALSE(cb_.saw_wait_finished_);
+ ASSERT_FALSE(cb_.saw_wait_start_);
+ {
+ Thread* self = Thread::Current();
+ self->TransitionFromSuspendedToRunnable();
+ bool started = runtime_->Start();
+ ASSERT_TRUE(started);
+ {
+ ScopedObjectAccess soa(self);
+ cb_.SetInterestingObject(
+ soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections).Ptr());
+ Monitor::Wait(
+ self,
+ // Just a random class
+ soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections).Ptr(),
+ /*ms*/0,
+ /*ns*/0,
+ /*interruptShouldThrow*/false,
+ /*why*/kWaiting);
+ }
+ }
+ ASSERT_TRUE(cb_.saw_wait_start_);
+ ASSERT_FALSE(cb_.saw_wait_finished_);
+}
+
} // namespace art
diff --git a/runtime/thread_state.h b/runtime/thread_state.h
index 8f2f70f..8edfeec 100644
--- a/runtime/thread_state.h
+++ b/runtime/thread_state.h
@@ -29,6 +29,8 @@
kSleeping, // TIMED_WAITING TS_SLEEPING in Thread.sleep()
kBlocked, // BLOCKED TS_MONITOR blocked on a monitor
kWaiting, // WAITING TS_WAIT in Object.wait()
+ kWaitingForLockInflation, // WAITING TS_WAIT blocked inflating a thin-lock
+ kWaitingForTaskProcessor, // WAITING TS_WAIT blocked waiting for taskProcessor
kWaitingForGcToComplete, // WAITING TS_WAIT blocked waiting for GC
kWaitingForCheckPointsToRun, // WAITING TS_WAIT GC waiting for checkpoints to run
kWaitingPerformingGc, // WAITING TS_WAIT performing GC