Inflate contended lock word by suspending owner.
Bug 6961405.
Don't inflate monitors for Notify and NotifyAll.
Tidy lock word, handle recursive lock case alongside unlocked case and move
assembly out of line (except for ARM quick). Also handle null in out-of-line
assembly as the test is quick and the enter/exit code is already a safepoint.
To gain ownership of a monitor on behalf of another thread, monitor contenders
must not hold the monitor_lock_, so they wait on a condition variable.
Reduce size of per mutex contention log.
Be consistent in calling thin lock thread ids just thread ids.
Fix potential thread death races caused by the use of FindThreadByThreadId,
make it invariant that returned threads are either self or suspended now.
Code size reduction on ARM boot.oat 0.2%.
Old nexus 7 speedup 0.25%, new nexus 7 speedup 1.4%, nexus 10 speedup 2.24%,
nexus 4 speedup 2.09% on DeltaBlue.
Change-Id: Id52558b914f160d9c8578fdd7fc8199a9598576a
diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc
index 44cf810..ff1ed2a 100644
--- a/runtime/thread_list.cc
+++ b/runtime/thread_list.cc
@@ -17,6 +17,8 @@
#include "thread_list.h"
#include <dirent.h>
+#include <ScopedLocalRef.h>
+#include <ScopedUtfChars.h>
#include <sys/types.h>
#include <unistd.h>
@@ -24,8 +26,13 @@
#include "base/mutex-inl.h"
#include "base/timing_logger.h"
#include "debugger.h"
+#include "jni_internal.h"
+#include "lock_word.h"
+#include "monitor.h"
+#include "scoped_thread_state_change.h"
#include "thread.h"
#include "utils.h"
+#include "well_known_classes.h"
namespace art {
@@ -33,6 +40,7 @@
: allocated_ids_lock_("allocated thread ids lock"),
suspend_all_count_(0), debug_suspend_all_count_(0),
thread_exit_cond_("thread exit condition variable", *Locks::thread_list_lock_) {
+ CHECK(Monitor::IsValidLockWord(LockWord::FromThinLockId(kMaxThreadId, 1)));
}
ThreadList::~ThreadList() {
@@ -160,18 +168,19 @@
// Call a checkpoint function for each thread, threads which are suspend get their checkpoint
// manually called.
MutexLock mu(self, *Locks::thread_list_lock_);
+ MutexLock mu2(self, *Locks::thread_suspend_count_lock_);
for (const auto& thread : list_) {
if (thread != self) {
- for (;;) {
+ while (true) {
if (thread->RequestCheckpoint(checkpoint_function)) {
// This thread will run it's checkpoint some time in the near future.
count++;
break;
} else {
// We are probably suspended, try to make sure that we stay suspended.
- MutexLock mu2(self, *Locks::thread_suspend_count_lock_);
// The thread switched back to runnable.
if (thread->GetState() == kRunnable) {
+ // Spurious fail, try again.
continue;
}
thread->ModifySuspendCount(self, +1, false);
@@ -204,7 +213,7 @@
}
}
// We know for sure that the thread is suspended at this point.
- thread->RunCheckpointFunction();
+ checkpoint_function->Run(thread);
{
MutexLock mu2(self, *Locks::thread_suspend_count_lock_);
thread->ModifySuspendCount(self, -1, false);
@@ -322,6 +331,178 @@
VLOG(threads) << "Resume(" << *thread << ") complete";
}
+static void ThreadSuspendByPeerWarning(Thread* self, int level, const char* message, jobject peer) {
+ JNIEnvExt* env = self->GetJniEnv();
+ ScopedLocalRef<jstring>
+ scoped_name_string(env, (jstring)env->GetObjectField(peer,
+ WellKnownClasses::java_lang_Thread_name));
+ ScopedUtfChars scoped_name_chars(env, scoped_name_string.get());
+ if (scoped_name_chars.c_str() == NULL) {
+ LOG(level) << message << ": " << peer;
+ env->ExceptionClear();
+ } else {
+ LOG(level) << message << ": " << peer << ":" << scoped_name_chars.c_str();
+ }
+}
+
+// Unlike suspending all threads where we can wait to acquire the mutator_lock_, suspending an
+// individual thread requires polling. delay_us is the requested sleep and total_delay_us
+// accumulates the total time spent sleeping for timeouts. The first sleep is just a yield,
+// subsequently sleeps increase delay_us from 1ms to 500ms by doubling.
+static void ThreadSuspendSleep(Thread* self, useconds_t* delay_us, useconds_t* total_delay_us) {
+ for (int i = kLockLevelCount - 1; i >= 0; --i) {
+ BaseMutex* held_mutex = self->GetHeldMutex(static_cast<LockLevel>(i));
+ if (held_mutex != NULL) {
+ LOG(FATAL) << "Holding " << held_mutex->GetName() << " while sleeping for thread suspension";
+ }
+ }
+ {
+ useconds_t new_delay_us = (*delay_us) * 2;
+ CHECK_GE(new_delay_us, *delay_us);
+ if (new_delay_us < 500000) { // Don't allow sleeping to be more than 0.5s.
+ *delay_us = new_delay_us;
+ }
+ }
+ if ((*delay_us) == 0) {
+ sched_yield();
+ // Default to 1 milliseconds (note that this gets multiplied by 2 before the first sleep).
+ (*delay_us) = 500;
+ } else {
+ usleep(*delay_us);
+ (*total_delay_us) += (*delay_us);
+ }
+}
+
+Thread* ThreadList::SuspendThreadByPeer(jobject peer, bool request_suspension,
+ bool debug_suspension, bool* timed_out) {
+ static const useconds_t kTimeoutUs = 30 * 1000000; // 30s.
+ useconds_t total_delay_us = 0;
+ useconds_t delay_us = 0;
+ bool did_suspend_request = false;
+ *timed_out = false;
+ Thread* self = Thread::Current();
+ while (true) {
+ Thread* thread;
+ {
+ ScopedObjectAccess soa(self);
+ MutexLock mu(self, *Locks::thread_list_lock_);
+ thread = Thread::FromManagedThread(soa, peer);
+ if (thread == NULL) {
+ ThreadSuspendByPeerWarning(self, WARNING, "No such thread for suspend", peer);
+ return NULL;
+ }
+ {
+ MutexLock mu(self, *Locks::thread_suspend_count_lock_);
+ if (request_suspension) {
+ thread->ModifySuspendCount(self, +1, debug_suspension);
+ request_suspension = false;
+ did_suspend_request = true;
+ } else {
+ // If the caller isn't requesting suspension, a suspension should have already occurred.
+ CHECK_GT(thread->GetSuspendCount(), 0);
+ }
+ // IsSuspended on the current thread will fail as the current thread is changed into
+ // Runnable above. As the suspend count is now raised if this is the current thread
+ // it will self suspend on transition to Runnable, making it hard to work with. It's simpler
+ // to just explicitly handle the current thread in the callers to this code.
+ CHECK_NE(thread, self) << "Attempt to suspend the current thread for the debugger";
+ // If thread is suspended (perhaps it was already not Runnable but didn't have a suspend
+ // count, or else we've waited and it has self suspended) or is the current thread, we're
+ // done.
+ if (thread->IsSuspended()) {
+ return thread;
+ }
+ if (total_delay_us >= kTimeoutUs) {
+ ThreadSuspendByPeerWarning(self, ERROR, "Thread suspension timed out", peer);
+ if (did_suspend_request) {
+ thread->ModifySuspendCount(soa.Self(), -1, debug_suspension);
+ }
+ *timed_out = true;
+ return NULL;
+ }
+ }
+ // Release locks and come out of runnable state.
+ }
+ ThreadSuspendSleep(self, &delay_us, &total_delay_us);
+ }
+}
+
+static void ThreadSuspendByThreadIdWarning(int level, const char* message, uint32_t thread_id) {
+ LOG(level) << StringPrintf("%s: %d", message, thread_id);
+}
+
+Thread* ThreadList::SuspendThreadByThreadId(uint32_t thread_id, bool debug_suspension,
+ bool* timed_out) {
+ static const useconds_t kTimeoutUs = 30 * 1000000; // 30s.
+ useconds_t total_delay_us = 0;
+ useconds_t delay_us = 0;
+ bool did_suspend_request = false;
+ *timed_out = false;
+ Thread* self = Thread::Current();
+ CHECK_NE(thread_id, kInvalidThreadId);
+ while (true) {
+ Thread* thread = NULL;
+ {
+ ScopedObjectAccess soa(self);
+ MutexLock mu(self, *Locks::thread_list_lock_);
+ for (const auto& it : list_) {
+ if (it->GetThreadId() == thread_id) {
+ thread = it;
+ break;
+ }
+ }
+ if (thread == NULL) {
+ // There's a race in inflating a lock and the owner giving up ownership and then dying.
+ ThreadSuspendByThreadIdWarning(WARNING, "No such thread id for suspend", thread_id);
+ return NULL;
+ }
+ {
+ MutexLock mu(self, *Locks::thread_suspend_count_lock_);
+ if (!did_suspend_request) {
+ thread->ModifySuspendCount(self, +1, debug_suspension);
+ did_suspend_request = true;
+ } else {
+ // If the caller isn't requesting suspension, a suspension should have already occurred.
+ CHECK_GT(thread->GetSuspendCount(), 0);
+ }
+ // IsSuspended on the current thread will fail as the current thread is changed into
+ // Runnable above. As the suspend count is now raised if this is the current thread
+ // it will self suspend on transition to Runnable, making it hard to work with. It's simpler
+ // to just explicitly handle the current thread in the callers to this code.
+ CHECK_NE(thread, self) << "Attempt to suspend the current thread for the debugger";
+ // If thread is suspended (perhaps it was already not Runnable but didn't have a suspend
+ // count, or else we've waited and it has self suspended) or is the current thread, we're
+ // done.
+ if (thread->IsSuspended()) {
+ return thread;
+ }
+ if (total_delay_us >= kTimeoutUs) {
+ ThreadSuspendByThreadIdWarning(ERROR, "Thread suspension timed out", thread_id);
+ if (did_suspend_request) {
+ thread->ModifySuspendCount(soa.Self(), -1, debug_suspension);
+ }
+ *timed_out = true;
+ return NULL;
+ }
+ }
+ // Release locks and come out of runnable state.
+ }
+ ThreadSuspendSleep(self, &delay_us, &total_delay_us);
+ }
+}
+
+Thread* ThreadList::FindThreadByThreadId(uint32_t thin_lock_id) {
+ Thread* self = Thread::Current();
+ MutexLock mu(self, *Locks::thread_list_lock_);
+ for (const auto& thread : list_) {
+ if (thread->GetThreadId() == thin_lock_id) {
+ CHECK(thread == self || thread->IsSuspended());
+ return thread;
+ }
+ }
+ return NULL;
+}
+
void ThreadList::SuspendAllForDebugger() {
Thread* self = Thread::Current();
Thread* debug_thread = Dbg::GetDebugThread();
@@ -528,8 +709,8 @@
// suspend and so on, must happen at this point, and not in ~Thread.
self->Destroy();
- uint32_t thin_lock_id = self->thin_lock_id_;
- self->thin_lock_id_ = 0;
+ uint32_t thin_lock_id = self->thin_lock_thread_id_;
+ self->thin_lock_thread_id_ = 0;
ReleaseThreadId(self, thin_lock_id);
while (self != NULL) {
// Remove and delete the Thread* while holding the thread_list_lock_ and
@@ -609,14 +790,4 @@
allocated_ids_.reset(id);
}
-Thread* ThreadList::FindThreadByThinLockId(uint32_t thin_lock_id) {
- MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
- for (const auto& thread : list_) {
- if (thread->GetThinLockId() == thin_lock_id) {
- return thread;
- }
- }
- return NULL;
-}
-
} // namespace art