Skip to content

Commit b42a816

Browse files
authored
Convert ThreadPlanStack's mutex to a shared mutex. (llvm#116438)
I have some reports of A/B inversion deadlocks between the ThreadPlanStack and the StackFrameList accesses. There's a fair bit of reasonable code in lldb that does "While accessing the ThreadPlanStack, look at that threads's StackFrameList", and also plenty of "While accessing the ThreadPlanStack, look at the StackFrameList." In all the cases I've seen so far, there was at most one of the locks taken that were trying to mutate the list, the other three were just reading. So we could solve the deadlock by converting the two mutexes over to shared mutexes. This patch is the easy part, the ThreadPlanStack mutex. The tricky part was because these were originally recursive mutexes, and recursive access to shared mutexes is undefined behavior according to the C++ standard, I had to add a couple NoLock variants to make sure it didn't get used recursively. Then since the only remaining calls are out to ThreadPlans and ThreadPlans don't have access to their containing ThreadPlanStack, converting this to a non-recursive lock should be safe.
1 parent cde4ae7 commit b42a816

File tree

2 files changed

+68
-53
lines changed

2 files changed

+68
-53
lines changed

lldb/include/lldb/Target/ThreadPlanStack.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include <unordered_map>
1515
#include <vector>
1616

17+
#include "llvm/Support/RWMutex.h"
18+
1719
#include "lldb/Target/Target.h"
1820
#include "lldb/Target/Thread.h"
1921
#include "lldb/lldb-private-forward.h"
@@ -96,9 +98,12 @@ class ThreadPlanStack {
9698
void ClearThreadCache();
9799

98100
private:
99-
void PrintOneStack(Stream &s, llvm::StringRef stack_name,
100-
const PlanStack &stack, lldb::DescriptionLevel desc_level,
101-
bool include_internal) const;
101+
lldb::ThreadPlanSP DiscardPlanNoLock();
102+
lldb::ThreadPlanSP GetCurrentPlanNoLock() const;
103+
void PrintOneStackNoLock(Stream &s, llvm::StringRef stack_name,
104+
const PlanStack &stack,
105+
lldb::DescriptionLevel desc_level,
106+
bool include_internal) const;
102107

103108
PlanStack m_plans; ///< The stack of plans this thread is executing.
104109
PlanStack m_completed_plans; ///< Plans that have been completed by this
@@ -110,7 +115,7 @@ class ThreadPlanStack {
110115
size_t m_completed_plan_checkpoint = 0; // Monotonically increasing token for
111116
// completed plan checkpoints.
112117
std::unordered_map<size_t, PlanStack> m_completed_plan_store;
113-
mutable std::recursive_mutex m_stack_mutex;
118+
mutable llvm::sys::RWMutex m_stack_mutex;
114119
};
115120

116121
class ThreadPlanStackMap {

lldb/source/Target/ThreadPlanStack.cpp

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,21 @@ ThreadPlanStack::ThreadPlanStack(const Thread &thread, bool make_null) {
3939
void ThreadPlanStack::DumpThreadPlans(Stream &s,
4040
lldb::DescriptionLevel desc_level,
4141
bool include_internal) const {
42-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
42+
llvm::sys::ScopedReader guard(m_stack_mutex);
4343
s.IndentMore();
44-
PrintOneStack(s, "Active plan stack", m_plans, desc_level, include_internal);
45-
PrintOneStack(s, "Completed plan stack", m_completed_plans, desc_level,
46-
include_internal);
47-
PrintOneStack(s, "Discarded plan stack", m_discarded_plans, desc_level,
48-
include_internal);
44+
PrintOneStackNoLock(s, "Active plan stack", m_plans, desc_level,
45+
include_internal);
46+
PrintOneStackNoLock(s, "Completed plan stack", m_completed_plans, desc_level,
47+
include_internal);
48+
PrintOneStackNoLock(s, "Discarded plan stack", m_discarded_plans, desc_level,
49+
include_internal);
4950
s.IndentLess();
5051
}
5152

52-
void ThreadPlanStack::PrintOneStack(Stream &s, llvm::StringRef stack_name,
53-
const PlanStack &stack,
54-
lldb::DescriptionLevel desc_level,
55-
bool include_internal) const {
56-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
53+
void ThreadPlanStack::PrintOneStackNoLock(Stream &s, llvm::StringRef stack_name,
54+
const PlanStack &stack,
55+
lldb::DescriptionLevel desc_level,
56+
bool include_internal) const {
5757
// If the stack is empty, just exit:
5858
if (stack.empty())
5959
return;
@@ -82,15 +82,15 @@ void ThreadPlanStack::PrintOneStack(Stream &s, llvm::StringRef stack_name,
8282
}
8383

8484
size_t ThreadPlanStack::CheckpointCompletedPlans() {
85-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
85+
llvm::sys::ScopedWriter guard(m_stack_mutex);
8686
m_completed_plan_checkpoint++;
8787
m_completed_plan_store.insert(
8888
std::make_pair(m_completed_plan_checkpoint, m_completed_plans));
8989
return m_completed_plan_checkpoint;
9090
}
9191

9292
void ThreadPlanStack::RestoreCompletedPlanCheckpoint(size_t checkpoint) {
93-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
93+
llvm::sys::ScopedWriter guard(m_stack_mutex);
9494
auto result = m_completed_plan_store.find(checkpoint);
9595
assert(result != m_completed_plan_store.end() &&
9696
"Asked for a checkpoint that didn't exist");
@@ -99,13 +99,13 @@ void ThreadPlanStack::RestoreCompletedPlanCheckpoint(size_t checkpoint) {
9999
}
100100

101101
void ThreadPlanStack::DiscardCompletedPlanCheckpoint(size_t checkpoint) {
102-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
102+
llvm::sys::ScopedWriter guard(m_stack_mutex);
103103
m_completed_plan_store.erase(checkpoint);
104104
}
105105

106106
void ThreadPlanStack::ThreadDestroyed(Thread *thread) {
107107
// Tell the plan stacks that this thread is going away:
108-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
108+
llvm::sys::ScopedWriter guard(m_stack_mutex);
109109
for (ThreadPlanSP plan : m_plans)
110110
plan->ThreadDestroyed();
111111

@@ -134,20 +134,22 @@ void ThreadPlanStack::PushPlan(lldb::ThreadPlanSP new_plan_sp) {
134134
// If the thread plan doesn't already have a tracer, give it its parent's
135135
// tracer:
136136
// The first plan has to be a base plan:
137-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
138-
assert((m_plans.size() > 0 || new_plan_sp->IsBasePlan()) &&
139-
"Zeroth plan must be a base plan");
140-
141-
if (!new_plan_sp->GetThreadPlanTracer()) {
142-
assert(!m_plans.empty());
143-
new_plan_sp->SetThreadPlanTracer(m_plans.back()->GetThreadPlanTracer());
137+
{ // Scope for Lock - DidPush often adds plans to the stack:
138+
llvm::sys::ScopedWriter guard(m_stack_mutex);
139+
assert((m_plans.size() > 0 || new_plan_sp->IsBasePlan()) &&
140+
"Zeroth plan must be a base plan");
141+
142+
if (!new_plan_sp->GetThreadPlanTracer()) {
143+
assert(!m_plans.empty());
144+
new_plan_sp->SetThreadPlanTracer(m_plans.back()->GetThreadPlanTracer());
145+
}
146+
m_plans.push_back(new_plan_sp);
144147
}
145-
m_plans.push_back(new_plan_sp);
146148
new_plan_sp->DidPush();
147149
}
148150

149151
lldb::ThreadPlanSP ThreadPlanStack::PopPlan() {
150-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
152+
llvm::sys::ScopedWriter guard(m_stack_mutex);
151153
assert(m_plans.size() > 1 && "Can't pop the base thread plan");
152154

153155
// Note that moving the top element of the vector would leave it in an
@@ -161,7 +163,11 @@ lldb::ThreadPlanSP ThreadPlanStack::PopPlan() {
161163
}
162164

163165
lldb::ThreadPlanSP ThreadPlanStack::DiscardPlan() {
164-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
166+
llvm::sys::ScopedWriter guard(m_stack_mutex);
167+
return DiscardPlanNoLock();
168+
}
169+
170+
lldb::ThreadPlanSP ThreadPlanStack::DiscardPlanNoLock() {
165171
assert(m_plans.size() > 1 && "Can't discard the base thread plan");
166172

167173
// Note that moving the top element of the vector would leave it in an
@@ -177,12 +183,12 @@ lldb::ThreadPlanSP ThreadPlanStack::DiscardPlan() {
177183
// If the input plan is nullptr, discard all plans. Otherwise make sure this
178184
// plan is in the stack, and if so discard up to and including it.
179185
void ThreadPlanStack::DiscardPlansUpToPlan(ThreadPlan *up_to_plan_ptr) {
180-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
186+
llvm::sys::ScopedWriter guard(m_stack_mutex);
181187
int stack_size = m_plans.size();
182188

183189
if (up_to_plan_ptr == nullptr) {
184190
for (int i = stack_size - 1; i > 0; i--)
185-
DiscardPlan();
191+
DiscardPlanNoLock();
186192
return;
187193
}
188194

@@ -197,23 +203,23 @@ void ThreadPlanStack::DiscardPlansUpToPlan(ThreadPlan *up_to_plan_ptr) {
197203
if (found_it) {
198204
bool last_one = false;
199205
for (int i = stack_size - 1; i > 0 && !last_one; i--) {
200-
if (GetCurrentPlan().get() == up_to_plan_ptr)
206+
if (GetCurrentPlanNoLock().get() == up_to_plan_ptr)
201207
last_one = true;
202-
DiscardPlan();
208+
DiscardPlanNoLock();
203209
}
204210
}
205211
}
206212

207213
void ThreadPlanStack::DiscardAllPlans() {
208-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
214+
llvm::sys::ScopedWriter guard(m_stack_mutex);
209215
int stack_size = m_plans.size();
210216
for (int i = stack_size - 1; i > 0; i--) {
211-
DiscardPlan();
217+
DiscardPlanNoLock();
212218
}
213219
}
214220

215221
void ThreadPlanStack::DiscardConsultingControllingPlans() {
216-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
222+
llvm::sys::ScopedWriter guard(m_stack_mutex);
217223
while (true) {
218224
int controlling_plan_idx;
219225
bool discard = true;
@@ -234,26 +240,30 @@ void ThreadPlanStack::DiscardConsultingControllingPlans() {
234240

235241
// First pop all the dependent plans:
236242
for (int i = m_plans.size() - 1; i > controlling_plan_idx; i--) {
237-
DiscardPlan();
243+
DiscardPlanNoLock();
238244
}
239245

240246
// Now discard the controlling plan itself.
241247
// The bottom-most plan never gets discarded. "OkayToDiscard" for it
242248
// means discard it's dependent plans, but not it...
243249
if (controlling_plan_idx > 0) {
244-
DiscardPlan();
250+
DiscardPlanNoLock();
245251
}
246252
}
247253
}
248254

249255
lldb::ThreadPlanSP ThreadPlanStack::GetCurrentPlan() const {
250-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
256+
llvm::sys::ScopedReader guard(m_stack_mutex);
257+
return GetCurrentPlanNoLock();
258+
}
259+
260+
lldb::ThreadPlanSP ThreadPlanStack::GetCurrentPlanNoLock() const {
251261
assert(m_plans.size() != 0 && "There will always be a base plan.");
252262
return m_plans.back();
253263
}
254264

255265
lldb::ThreadPlanSP ThreadPlanStack::GetCompletedPlan(bool skip_private) const {
256-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
266+
llvm::sys::ScopedReader guard(m_stack_mutex);
257267
if (m_completed_plans.empty())
258268
return {};
259269

@@ -271,7 +281,7 @@ lldb::ThreadPlanSP ThreadPlanStack::GetCompletedPlan(bool skip_private) const {
271281

272282
lldb::ThreadPlanSP ThreadPlanStack::GetPlanByIndex(uint32_t plan_idx,
273283
bool skip_private) const {
274-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
284+
llvm::sys::ScopedReader guard(m_stack_mutex);
275285
uint32_t idx = 0;
276286

277287
for (lldb::ThreadPlanSP plan_sp : m_plans) {
@@ -285,7 +295,7 @@ lldb::ThreadPlanSP ThreadPlanStack::GetPlanByIndex(uint32_t plan_idx,
285295
}
286296

287297
lldb::ValueObjectSP ThreadPlanStack::GetReturnValueObject() const {
288-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
298+
llvm::sys::ScopedReader guard(m_stack_mutex);
289299
if (m_completed_plans.empty())
290300
return {};
291301

@@ -299,7 +309,7 @@ lldb::ValueObjectSP ThreadPlanStack::GetReturnValueObject() const {
299309
}
300310

301311
lldb::ExpressionVariableSP ThreadPlanStack::GetExpressionVariable() const {
302-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
312+
llvm::sys::ScopedReader guard(m_stack_mutex);
303313
if (m_completed_plans.empty())
304314
return {};
305315

@@ -312,23 +322,23 @@ lldb::ExpressionVariableSP ThreadPlanStack::GetExpressionVariable() const {
312322
return {};
313323
}
314324
bool ThreadPlanStack::AnyPlans() const {
315-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
325+
llvm::sys::ScopedReader guard(m_stack_mutex);
316326
// There is always a base plan...
317327
return m_plans.size() > 1;
318328
}
319329

320330
bool ThreadPlanStack::AnyCompletedPlans() const {
321-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
331+
llvm::sys::ScopedReader guard(m_stack_mutex);
322332
return !m_completed_plans.empty();
323333
}
324334

325335
bool ThreadPlanStack::AnyDiscardedPlans() const {
326-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
336+
llvm::sys::ScopedReader guard(m_stack_mutex);
327337
return !m_discarded_plans.empty();
328338
}
329339

330340
bool ThreadPlanStack::IsPlanDone(ThreadPlan *in_plan) const {
331-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
341+
llvm::sys::ScopedReader guard(m_stack_mutex);
332342
for (auto plan : m_completed_plans) {
333343
if (plan.get() == in_plan)
334344
return true;
@@ -337,7 +347,7 @@ bool ThreadPlanStack::IsPlanDone(ThreadPlan *in_plan) const {
337347
}
338348

339349
bool ThreadPlanStack::WasPlanDiscarded(ThreadPlan *in_plan) const {
340-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
350+
llvm::sys::ScopedReader guard(m_stack_mutex);
341351
for (auto plan : m_discarded_plans) {
342352
if (plan.get() == in_plan)
343353
return true;
@@ -346,7 +356,7 @@ bool ThreadPlanStack::WasPlanDiscarded(ThreadPlan *in_plan) const {
346356
}
347357

348358
ThreadPlan *ThreadPlanStack::GetPreviousPlan(ThreadPlan *current_plan) const {
349-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
359+
llvm::sys::ScopedReader guard(m_stack_mutex);
350360
if (current_plan == nullptr)
351361
return nullptr;
352362

@@ -361,7 +371,7 @@ ThreadPlan *ThreadPlanStack::GetPreviousPlan(ThreadPlan *current_plan) const {
361371
// If this is the first completed plan, the previous one is the
362372
// bottom of the regular plan stack.
363373
if (stack_size > 0 && m_completed_plans[0].get() == current_plan) {
364-
return GetCurrentPlan().get();
374+
return GetCurrentPlanNoLock().get();
365375
}
366376

367377
// Otherwise look for it in the regular plans.
@@ -374,7 +384,7 @@ ThreadPlan *ThreadPlanStack::GetPreviousPlan(ThreadPlan *current_plan) const {
374384
}
375385

376386
ThreadPlan *ThreadPlanStack::GetInnermostExpression() const {
377-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
387+
llvm::sys::ScopedReader guard(m_stack_mutex);
378388
int stack_size = m_plans.size();
379389

380390
for (int i = stack_size - 1; i > 0; i--) {
@@ -385,13 +395,13 @@ ThreadPlan *ThreadPlanStack::GetInnermostExpression() const {
385395
}
386396

387397
void ThreadPlanStack::ClearThreadCache() {
388-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
398+
llvm::sys::ScopedReader guard(m_stack_mutex);
389399
for (lldb::ThreadPlanSP thread_plan_sp : m_plans)
390400
thread_plan_sp->ClearThreadCache();
391401
}
392402

393403
void ThreadPlanStack::WillResume() {
394-
std::lock_guard<std::recursive_mutex> guard(m_stack_mutex);
404+
llvm::sys::ScopedWriter guard(m_stack_mutex);
395405
m_completed_plans.clear();
396406
m_discarded_plans.clear();
397407
}

0 commit comments

Comments
 (0)