[Lldb-commits] [lldb] New ThreadPlanSingleThreadTimeout to resolve potential deadlock in single thread stepping (PR #90930)

via lldb-commits lldb-commits at lists.llvm.org
Fri Jun 28 15:43:31 PDT 2024


================
@@ -0,0 +1,217 @@
+//===-- ThreadPlanStepOverRange.cpp ---------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Target/ThreadPlanSingleThreadTimeout.h"
+#include "lldb/Symbol/Block.h"
+#include "lldb/Symbol/CompileUnit.h"
+#include "lldb/Symbol/Function.h"
+#include "lldb/Symbol/LineTable.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Target/RegisterContext.h"
+#include "lldb/Target/Target.h"
+#include "lldb/Target/Thread.h"
+#include "lldb/Target/ThreadPlanStepOut.h"
+#include "lldb/Target/ThreadPlanStepThrough.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+#include "lldb/Utility/Stream.h"
+
+using namespace lldb_private;
+using namespace lldb;
+
+ThreadPlanSingleThreadTimeout::ThreadPlanSingleThreadTimeout(Thread &thread,
+                                                             TimeoutInfo &info)
+    : ThreadPlan(ThreadPlan::eKindSingleThreadTimeout, "Single thread timeout",
+                 thread, eVoteNo, eVoteNoOpinion),
+      m_info(info), m_state(State::WaitTimeout), m_exit_flag(false) {
+  m_timer_thread = std::thread(TimeoutThreadFunc, this);
+  m_info.m_instance = this;
+  m_state = m_info.m_last_state;
+}
+
+ThreadPlanSingleThreadTimeout::~ThreadPlanSingleThreadTimeout() {
+  m_info.m_instance = nullptr;
+  if (m_state == State::Done)
+    m_state = State::WaitTimeout;
+}
+
+void ThreadPlanSingleThreadTimeout::GetDescription(
+    Stream *s, lldb::DescriptionLevel level) {
+  s->Printf("Single thread timeout, state(%s)", StateToString(m_state).c_str());
+}
+
+std::string ThreadPlanSingleThreadTimeout::StateToString(State state) {
+  switch (state) {
+  case State::WaitTimeout:
+    return "WaitTimeout";
+  case State::AsyncInterrupt:
+    return "AsyncInterrupt";
+  case State::Done:
+    return "Done";
+  }
+}
+
+void ThreadPlanSingleThreadTimeout::PushNewWithTimeout(Thread &thread,
+                                                       TimeoutInfo &info) {
+  uint64_t timeout_in_ms = thread.GetSingleThreadPlanTimeout();
+  if (timeout_in_ms == 0)
+    return;
+
+  // Do not create timeout if we are not stopping other threads.
+  if (!thread.GetCurrentPlan()->StopOthers())
+    return;
+
+  auto timeout_plan = new ThreadPlanSingleThreadTimeout(thread, info);
+  ThreadPlanSP thread_plan_sp(timeout_plan);
+  auto status = thread.QueueThreadPlan(thread_plan_sp,
+                                       /*abort_other_plans*/ false);
+  Log *log = GetLog(LLDBLog::Step);
+  LLDB_LOGF(log, "ThreadPlanSingleThreadTimeout pushing a brand new one");
+}
+
+void ThreadPlanSingleThreadTimeout::ResumeFromPrevState(Thread &thread,
+                                                        TimeoutInfo &info) {
+  uint64_t timeout_in_ms = thread.GetSingleThreadPlanTimeout();
+  if (timeout_in_ms == 0)
+    return;
+
+  if (info.m_instance != nullptr)
+    return;
+
+  // Do not create timeout if we are not stopping other threads.
+  if (!thread.GetCurrentPlan()->StopOthers())
+    return;
+
+  auto timeout_plan = new ThreadPlanSingleThreadTimeout(thread, info);
+  ThreadPlanSP thread_plan_sp(timeout_plan);
+  auto status = thread.QueueThreadPlan(thread_plan_sp,
+                                       /*abort_other_plans*/ false);
+  Log *log = GetLog(LLDBLog::Step);
+  LLDB_LOGF(log, "ThreadPlanSingleThreadTimeout reset from previous state");
+}
+
+bool ThreadPlanSingleThreadTimeout::WillStop() {
+  Log *log = GetLog(LLDBLog::Step);
+  LLDB_LOGF(log, "ThreadPlanSingleThreadTimeout::WillStop().");
+
+  // Reset the state during stop.
+  m_info.m_last_state = State::WaitTimeout;
+  m_info.m_instance = this;
+  return true;
+}
+
+void ThreadPlanSingleThreadTimeout::DidPop() {
+  Log *log = GetLog(LLDBLog::Step);
+  {
+    std::lock_guard<std::mutex> lock(m_mutex);
+    LLDB_LOGF(log, "ThreadPlanSingleThreadTimeout::DidPop().");
+    // Tell timer thread to exit.
+    m_exit_flag = true;
+  }
+  m_wakeup_cv.notify_one();
+  // Wait for timer thread to exit.
+  m_timer_thread.join();
+}
+
+bool ThreadPlanSingleThreadTimeout::DoPlanExplainsStop(Event *event_ptr) {
+  lldb::StateType stop_state =
+      Process::ProcessEventData::GetStateFromEvent(event_ptr);
+  Log *log = GetLog(LLDBLog::Step);
+  LLDB_LOGF(
+      log,
+      "ThreadPlanSingleThreadTimeout::DoPlanExplainsStop(): got event: %s.",
+      StateAsCString(stop_state));
+  return true;
+}
+
+lldb::StateType ThreadPlanSingleThreadTimeout::GetPlanRunState() {
+  return GetPreviousPlan()->GetPlanRunState();
+}
+
+void ThreadPlanSingleThreadTimeout::TimeoutThreadFunc(
+    ThreadPlanSingleThreadTimeout *self) {
+  std::unique_lock<std::mutex> lock(self->m_mutex);
+  uint64_t timeout_in_ms = self->GetThread().GetSingleThreadPlanTimeout();
+  self->m_wakeup_cv.wait_for(lock, std::chrono::milliseconds(timeout_in_ms),
----------------
jimingham wrote:

It would be good to log both before and after the wait call here.  I think that will help read the step logs.

https://github.com/llvm/llvm-project/pull/90930


More information about the lldb-commits mailing list