Merge "Avoid using ThreadList::Dump() in empty checkpoint timeout."
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index b889913..dccec9f 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -843,8 +843,26 @@
         ss << "\n";
         Locks::mutator_lock_->Dump(ss);
         ss << "\n";
-        runtime->GetThreadList()->Dump(ss);
-        LOG(FATAL) << ss.str();
+        LOG(FATAL_WITHOUT_ABORT) << ss.str();
+        // Some threads in 'runnable_thread_ids' are probably stuck. Try to dump their stacks.
+        // Avoid using ThreadList::Dump() because it is likely to get stuck as well.
+        {
+          ReaderMutexLock mu0(self, *Locks::mutator_lock_);
+          MutexLock mu1(self, *Locks::thread_list_lock_);
+          for (Thread* thread : runtime->GetThreadList()->GetList()) {
+            uint32_t tid = thread->GetThreadId();
+            bool is_in_runnable_thread_ids =
+                std::find(runnable_thread_ids.begin(), runnable_thread_ids.end(), tid) !=
+                runnable_thread_ids.end();
+            if (is_in_runnable_thread_ids &&
+                thread->ReadFlag(kEmptyCheckpointRequest)) {
+              // Found a runnable thread that hasn't responded to the empty checkpoint request.
+              // Assume it's stuck and safe to dump its stack.
+              thread->Dump(LOG_STREAM(FATAL_WITHOUT_ABORT));
+            }
+          }
+        }
+        LOG(FATAL) << "Dumped runnable threads that haven't responded to empty checkpoint.";
       }
     } else {
       barrier->Increment(self, barrier_count);