-
Notifications
You must be signed in to change notification settings - Fork 14.3k
New ThreadPlanSingleThreadTimeout to resolve potential deadlock in single thread stepping #90930
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8746990
8a8ea12
a42d7d1
9d1b108
0e90389
56e99e4
92cb3ab
261d77d
2d6e565
82e1237
fabd27b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -302,7 +302,8 @@ class ThreadPlan : public std::enable_shared_from_this<ThreadPlan>, | |
eKindStepInRange, | ||
eKindRunToAddress, | ||
eKindStepThrough, | ||
eKindStepUntil | ||
eKindStepUntil, | ||
eKindSingleThreadTimeout, | ||
}; | ||
|
||
virtual ~ThreadPlan(); | ||
|
@@ -395,6 +396,11 @@ class ThreadPlan : public std::enable_shared_from_this<ThreadPlan>, | |
|
||
bool IsControllingPlan() { return m_is_controlling_plan; } | ||
|
||
// Returns true if this plan is a leaf plan, meaning the plan will be popped | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leaf plans are only auto-popped if they don't explain the stop, right? Might be worth mentioning that here. |
||
// during each stop if it does not explain the stop and re-pushed before | ||
// resuming to stay at the top of the stack. | ||
virtual bool IsLeafPlan() { return false; } | ||
|
||
bool SetIsControllingPlan(bool value) { | ||
bool old_value = m_is_controlling_plan; | ||
m_is_controlling_plan = value; | ||
|
@@ -483,6 +489,8 @@ class ThreadPlan : public std::enable_shared_from_this<ThreadPlan>, | |
return m_takes_iteration_count; | ||
} | ||
|
||
virtual lldb::StateType GetPlanRunState() = 0; | ||
|
||
protected: | ||
// Constructors and Destructors | ||
ThreadPlan(ThreadPlanKind kind, const char *name, Thread &thread, | ||
|
@@ -522,8 +530,6 @@ class ThreadPlan : public std::enable_shared_from_this<ThreadPlan>, | |
GetThread().SetStopInfo(stop_reason_sp); | ||
} | ||
|
||
virtual lldb::StateType GetPlanRunState() = 0; | ||
|
||
bool IsUsuallyUnexplainedStopReason(lldb::StopReason); | ||
|
||
Status m_status; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
//===-- ThreadPlanSingleThreadTimeout.h -------------------------*- C++ -*-===// | ||
// | ||
// 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 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#ifndef LLDB_TARGET_THREADPLANSINGLETHREADTIMEOUT_H | ||
#define LLDB_TARGET_THREADPLANSINGLETHREADTIMEOUT_H | ||
|
||
#include "lldb/Target/Thread.h" | ||
#include "lldb/Target/ThreadPlan.h" | ||
#include "lldb/Utility/Event.h" | ||
#include "lldb/Utility/LLDBLog.h" | ||
#include "lldb/Utility/State.h" | ||
|
||
#include <chrono> | ||
#include <thread> | ||
|
||
namespace lldb_private { | ||
|
||
// | ||
// Thread plan used by single thread execution to issue timeout. This is useful | ||
// to detect potential deadlock in single thread execution. The timeout measures | ||
// the elapsed time from the last internal stop and gets reset by each internal | ||
// stop to ensure we are accurately detecting execution not moving forward. | ||
// This means this thread plan may be created/destroyed multiple times by the | ||
// parent execution plan. | ||
// | ||
// When a timeout happens, the thread plan resolves the potential deadlock by | ||
// issuing a thread specific async interrupt to enter stop state, then execution | ||
// is resumed with all threads running to resolve the potential deadlock | ||
// | ||
class ThreadPlanSingleThreadTimeout : public ThreadPlan { | ||
enum class State { | ||
WaitTimeout, // Waiting for timeout. | ||
AsyncInterrupt, // Async interrupt has been issued. | ||
Done, // Finished resume all threads. | ||
}; | ||
|
||
public: | ||
// TODO: allow timeout to be set on per thread plan basis. | ||
struct TimeoutInfo { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't something you need to do in this patch, but it would be good to allow different thread plan invocations to use different timeouts, and not just depend on the thread timeout. For instance, we should be able to reimplement RunThreadPlan with this infrastructure. But the timeouts you want for expression evaluation and stepping are likely to be very different (and different executions also use different timeouts). So being able to pass in the timeout, rather than relying on one thread specific one will be necessary. We could for instance add the timeout to the TimeoutInfo and plumb that so that the ThreadPlan that mixes in TimeoutResumeAll can pass in a timeout. |
||
// Whether there is a ThreadPlanSingleThreadTimeout instance alive. | ||
bool m_isAlive = false; | ||
ThreadPlanSingleThreadTimeout::State m_last_state = State::WaitTimeout; | ||
}; | ||
|
||
~ThreadPlanSingleThreadTimeout() override; | ||
|
||
// If input \param thread is running in single thread mode, push a | ||
// new ThreadPlanSingleThreadTimeout based on timeout setting from fresh new | ||
// state. The reference of \param info is passed in so that when | ||
// ThreadPlanSingleThreadTimeout got popped its last state can be stored | ||
// in it for future resume. | ||
static void PushNewWithTimeout(Thread &thread, TimeoutInfo &info); | ||
|
||
// Push a new ThreadPlanSingleThreadTimeout by restoring state from | ||
// input \param info and resume execution. | ||
static void ResumeFromPrevState(Thread &thread, TimeoutInfo &info); | ||
|
||
void GetDescription(Stream *s, lldb::DescriptionLevel level) override; | ||
bool ValidatePlan(Stream *error) override { return true; } | ||
bool WillStop() override; | ||
void DidPop() override; | ||
|
||
bool IsLeafPlan() override { return true; } | ||
bool DoPlanExplainsStop(Event *event_ptr) override; | ||
|
||
lldb::StateType GetPlanRunState() override; | ||
static void TimeoutThreadFunc(ThreadPlanSingleThreadTimeout *self); | ||
|
||
bool MischiefManaged() override; | ||
|
||
bool ShouldStop(Event *event_ptr) override; | ||
void SetStopOthers(bool new_value) override; | ||
bool StopOthers() override; | ||
|
||
private: | ||
ThreadPlanSingleThreadTimeout(Thread &thread, TimeoutInfo &info); | ||
|
||
bool IsTimeoutAsyncInterrupt(Event *event_ptr); | ||
bool HandleEvent(Event *event_ptr); | ||
void HandleTimeout(); | ||
uint64_t GetRemainingTimeoutMilliSeconds(); | ||
|
||
static std::string StateToString(State state); | ||
|
||
ThreadPlanSingleThreadTimeout(const ThreadPlanSingleThreadTimeout &) = delete; | ||
const ThreadPlanSingleThreadTimeout & | ||
operator=(const ThreadPlanSingleThreadTimeout &) = delete; | ||
|
||
TimeoutInfo &m_info; // Reference to controlling ThreadPlan's TimeoutInfo. | ||
State m_state; | ||
|
||
// Lock for m_wakeup_cv and m_exit_flag between thread plan thread and timer | ||
// thread | ||
std::mutex m_mutex; | ||
std::condition_variable m_wakeup_cv; | ||
std::thread m_timer_thread; | ||
std::chrono::steady_clock::time_point m_timeout_start; | ||
}; | ||
|
||
} // namespace lldb_private | ||
|
||
#endif // LLDB_TARGET_THREADPLANSINGLETHREADTIMEOUT_H |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,8 +58,15 @@ class ThreadPlanStepRange : public ThreadPlan { | |
// run' plan, then just single step. | ||
bool SetNextBranchBreakpoint(); | ||
|
||
// Whether the input stop info is caused by the next branch breakpoint. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This routine does NOT distinguish between the cases where the next branch breakpoint is the ONLY breakpoint at this site, and where it's one of many breakpoints that share this site. Later, you distinguish between the two cases where this is used (e.g. in ThreadPlanStepRange::NextRangeBreakpointExplainsStop. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function only checks the branch breakpoint without caring whether its site is shared by others or not. Let me add a comment. |
||
// Note: this does not check if branch breakpoint site is shared by other | ||
// breakpoints or not. | ||
bool IsNextBranchBreakpointStop(lldb::StopInfoSP stop_info_sp); | ||
|
||
void ClearNextBranchBreakpoint(); | ||
|
||
void ClearNextBranchBreakpointExplainedStop(); | ||
|
||
bool NextRangeBreakpointExplainsStop(lldb::StopInfoSP stop_info_sp); | ||
|
||
SymbolContext m_addr_context; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
//===-- TimeoutResumeAll.h -------------------------*- C++ -*-===// | ||
// | ||
// 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 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#ifndef LLDB_TARGET_TIMEOUTRESUMEALL_H | ||
#define LLDB_TARGET_TIMEOUTRESUMEALL_H | ||
|
||
#include "lldb/Target/ThreadPlanSingleThreadTimeout.h" | ||
|
||
namespace lldb_private { | ||
|
||
// Mixin class that provides the capability for ThreadPlan to support single | ||
// thread execution that resumes all threads after a timeout. | ||
// Opt-in thread plan should call PushNewTimeout() in its DidPush() and | ||
// ResumeWithTimeout() during DoWillResume(). | ||
class TimeoutResumeAll { | ||
public: | ||
TimeoutResumeAll(Thread &thread) : m_thread(thread) {} | ||
|
||
void PushNewTimeout() { | ||
ThreadPlanSingleThreadTimeout::PushNewWithTimeout(m_thread, m_timeout_info); | ||
} | ||
|
||
void ResumeWithTimeout() { | ||
ThreadPlanSingleThreadTimeout::ResumeFromPrevState(m_thread, | ||
m_timeout_info); | ||
} | ||
|
||
private: | ||
Thread &m_thread; | ||
ThreadPlanSingleThreadTimeout::TimeoutInfo m_timeout_info; | ||
}; | ||
|
||
} // namespace lldb_private | ||
|
||
#endif // LLDB_TARGET_TIMEOUTRESUMEALL_H |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should say what the
description
string is for.