summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--runtime/base/mutex.cc6
-rw-r--r--runtime/base/mutex.h5
-rw-r--r--runtime/thread_list.cc67
-rw-r--r--runtime/thread_list.h4
4 files changed, 49 insertions, 33 deletions
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc
index 6e00cc79b5..13dcb8c634 100644
--- a/runtime/base/mutex.cc
+++ b/runtime/base/mutex.cc
@@ -57,6 +57,7 @@ Mutex* Locks::reference_queue_soft_references_lock_ = nullptr;
Mutex* Locks::reference_queue_weak_references_lock_ = nullptr;
Mutex* Locks::runtime_shutdown_lock_ = nullptr;
Mutex* Locks::thread_list_lock_ = nullptr;
+ConditionVariable* Locks::thread_exit_cond_ = nullptr;
Mutex* Locks::thread_suspend_count_lock_ = nullptr;
Mutex* Locks::trace_lock_ = nullptr;
Mutex* Locks::unexpected_signal_lock_ = nullptr;
@@ -1063,8 +1064,13 @@ void Locks::Init() {
logging_lock_ = new Mutex("logging lock", current_lock_level, true);
#undef UPDATE_CURRENT_LOCK_LEVEL
+
+ InitConditions();
}
}
+void Locks::InitConditions() {
+ thread_exit_cond_ = new ConditionVariable("thread exit condition variable", *thread_list_lock_);
+}
} // namespace art
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index 45d2347ee2..3b052c0615 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -487,7 +487,7 @@ class SCOPED_LOCKABLE WriterMutexLock {
class Locks {
public:
static void Init();
-
+ static void InitConditions() NO_THREAD_SAFETY_ANALYSIS; // Condition variables.
// Guards allocation entrypoint instrumenting.
static Mutex* instrument_entrypoints_lock_;
@@ -575,6 +575,9 @@ class Locks {
// attaching and detaching.
static Mutex* thread_list_lock_ ACQUIRED_AFTER(deoptimization_lock_);
+ // Signaled when threads terminate. Used to determine when all non-daemons have terminated.
+ static ConditionVariable* thread_exit_cond_ GUARDED_BY(Locks::thread_list_lock_);
+
// Guards maintaining loading library data structures.
static Mutex* jni_libraries_lock_ ACQUIRED_AFTER(thread_list_lock_);
diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc
index 83c5ffb135..31ccbcc5cf 100644
--- a/runtime/thread_list.cc
+++ b/runtime/thread_list.cc
@@ -51,8 +51,7 @@ static constexpr useconds_t kThreadSuspendMaxYieldUs = 3000;
static constexpr useconds_t kThreadSuspendMaxSleepUs = 5000;
ThreadList::ThreadList()
- : suspend_all_count_(0), debug_suspend_all_count_(0),
- thread_exit_cond_("thread exit condition variable", *Locks::thread_list_lock_),
+ : suspend_all_count_(0), debug_suspend_all_count_(0), unregistering_count_(0),
suspend_all_historam_("suspend all histogram", 16, 64) {
CHECK(Monitor::IsValidLockWord(LockWord::FromThinLockId(kMaxThreadId, 1)));
}
@@ -70,7 +69,6 @@ ThreadList::~ThreadList() {
if (contains) {
Runtime::Current()->DetachCurrentThread();
}
-
WaitForOtherNonDaemonThreadsToExit();
// TODO: there's an unaddressed race here where a thread may attach during shutdown, see
// Thread::Init.
@@ -1002,27 +1000,32 @@ void ThreadList::UndoDebuggerSuspensions() {
void ThreadList::WaitForOtherNonDaemonThreadsToExit() {
Thread* self = Thread::Current();
Locks::mutator_lock_->AssertNotHeld(self);
- bool all_threads_are_daemons;
- do {
+ while (true) {
{
// No more threads can be born after we start to shutdown.
MutexLock mu(self, *Locks::runtime_shutdown_lock_);
CHECK(Runtime::Current()->IsShuttingDownLocked());
CHECK_EQ(Runtime::Current()->NumberOfThreadsBeingBorn(), 0U);
}
- all_threads_are_daemons = true;
MutexLock mu(self, *Locks::thread_list_lock_);
- for (const auto& thread : list_) {
- if (thread != self && !thread->IsDaemon()) {
- all_threads_are_daemons = false;
- break;
+ // Also wait for any threads that are unregistering to finish. This is required so that no
+ // threads access the thread list after it is deleted. TODO: This may not work for user daemon
+ // threads since they could unregister at the wrong time.
+ bool done = unregistering_count_ == 0;
+ if (done) {
+ for (const auto& thread : list_) {
+ if (thread != self && !thread->IsDaemon()) {
+ done = false;
+ break;
+ }
}
}
- if (!all_threads_are_daemons) {
- // Wait for another thread to exit before re-checking.
- thread_exit_cond_.Wait(self);
+ if (done) {
+ break;
}
- } while (!all_threads_are_daemons);
+ // Wait for another thread to exit before re-checking.
+ Locks::thread_exit_cond_->Wait(self);
+ }
}
void ThreadList::SuspendAllDaemonThreads() {
@@ -1092,42 +1095,45 @@ void ThreadList::Unregister(Thread* self) {
VLOG(threads) << "ThreadList::Unregister() " << *self;
+ {
+ MutexLock mu(self, *Locks::thread_list_lock_);
+ ++unregistering_count_;
+ }
+
// Any time-consuming destruction, plus anything that can call back into managed code or
- // suspend and so on, must happen at this point, and not in ~Thread.
+ // suspend and so on, must happen at this point, and not in ~Thread. The self->Destroy is what
+ // causes the threads to join. It is important to do this after incrementing unregistering_count_
+ // since we want the runtime to wait for the daemon threads to exit before deleting the thread
+ // list.
self->Destroy();
// If tracing, remember thread id and name before thread exits.
Trace::StoreExitingThreadInfo(self);
uint32_t thin_lock_id = self->GetThreadId();
- while (self != nullptr) {
+ while (true) {
// Remove and delete the Thread* while holding the thread_list_lock_ and
// thread_suspend_count_lock_ so that the unregistering thread cannot be suspended.
// Note: deliberately not using MutexLock that could hold a stale self pointer.
- Locks::thread_list_lock_->ExclusiveLock(self);
- bool removed = true;
+ MutexLock mu(self, *Locks::thread_list_lock_);
if (!Contains(self)) {
std::string thread_name;
self->GetThreadName(thread_name);
std::ostringstream os;
DumpNativeStack(os, GetTid(), " native: ", nullptr);
LOG(ERROR) << "Request to unregister unattached thread " << thread_name << "\n" << os.str();
+ break;
} else {
- Locks::thread_suspend_count_lock_->ExclusiveLock(self);
+ MutexLock mu2(self, *Locks::thread_suspend_count_lock_);
if (!self->IsSuspended()) {
list_.remove(self);
- } else {
- // We failed to remove the thread due to a suspend request, loop and try again.
- removed = false;
+ break;
}
- Locks::thread_suspend_count_lock_->ExclusiveUnlock(self);
- }
- Locks::thread_list_lock_->ExclusiveUnlock(self);
- if (removed) {
- delete self;
- self = nullptr;
}
+ // We failed to remove the thread due to a suspend request, loop and try again.
}
+ delete self;
+
// Release the thread ID after the thread is finished and deleted to avoid cases where we can
// temporarily have multiple threads with the same thread id. When this occurs, it causes
// problems in FindThreadByThreadId / SuspendThreadByThreadId.
@@ -1138,8 +1144,9 @@ void ThreadList::Unregister(Thread* self) {
CHECK_PTHREAD_CALL(pthread_setspecific, (Thread::pthread_key_self_, NULL), "detach self");
// Signal that a thread just detached.
- MutexLock mu(NULL, *Locks::thread_list_lock_);
- thread_exit_cond_.Signal(NULL);
+ MutexLock mu(nullptr, *Locks::thread_list_lock_);
+ --unregistering_count_;
+ Locks::thread_exit_cond_->Broadcast(nullptr);
}
void ThreadList::ForEach(void (*callback)(Thread*, void*), void* context) {
diff --git a/runtime/thread_list.h b/runtime/thread_list.h
index d18315aab4..de0dd7983c 100644
--- a/runtime/thread_list.h
+++ b/runtime/thread_list.h
@@ -177,8 +177,8 @@ class ThreadList {
int suspend_all_count_ GUARDED_BY(Locks::thread_suspend_count_lock_);
int debug_suspend_all_count_ GUARDED_BY(Locks::thread_suspend_count_lock_);
- // Signaled when threads terminate. Used to determine when all non-daemons have terminated.
- ConditionVariable thread_exit_cond_ GUARDED_BY(Locks::thread_list_lock_);
+ // Number of threads unregistering, ~ThreadList blocks until this hits 0.
+ int unregistering_count_ GUARDED_BY(Locks::thread_list_lock_);
// Thread suspend time histogram. Only modified when all the threads are suspended, so guarding
// by mutator lock ensures no thread can read when another thread is modifying it.