Skip to content

Commit 186fac3

Browse files
authored
Convert the StackFrameList mutex to a shared mutex. (llvm#117252)
In fact, there's only one public API in StackFrameList that changes the list explicitly. The rest only change the list if you happen to ask for more frames than lldb has currently fetched and that always adds frames "behind the user's back". So we were much more prone to deadlocking than we needed to be. This patch uses a shared_mutex instead, and when we have to add more frames (in GetFramesUpTo) we switches to exclusive long enough to add the frames, then goes back to shared. Most of the work here was actually getting the stack frame list locking to not require a recursive mutex (shared mutexes aren't recursive). I also added a test that has 5 threads progressively asking for more frames simultaneously to make sure we get back valid frames and don't deadlock.
1 parent 7141837 commit 186fac3

File tree

6 files changed

+298
-132
lines changed

6 files changed

+298
-132
lines changed

lldb/include/lldb/Target/StackFrameList.h

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include <memory>
1313
#include <mutex>
14+
#include <shared_mutex>
1415
#include <vector>
1516

1617
#include "lldb/Target/StackFrame.h"
@@ -94,24 +95,36 @@ class StackFrameList {
9495
bool show_unique = false, bool show_hidden = false,
9596
const char *frame_marker = nullptr);
9697

98+
/// Returns whether we have currently fetched all the frames of a stack.
99+
bool WereAllFramesFetched() const;
100+
97101
protected:
98102
friend class Thread;
99103
friend class ScriptedThread;
100104

105+
/// Use this API to build a stack frame list (used for scripted threads, for
106+
/// instance.) This API is not meant for StackFrameLists that have unwinders
107+
/// and partake in lazy stack filling (using GetFramesUpTo). Rather if you
108+
/// are building StackFrameLists with this API, you should build the entire
109+
/// list before making it available for use.
101110
bool SetFrameAtIndex(uint32_t idx, lldb::StackFrameSP &frame_sp);
102111

103-
/// Realizes frames up to (and including) end_idx (which can be greater than
104-
/// the actual number of frames.)
112+
/// Ensures that frames up to (and including) `end_idx` are realized in the
113+
/// StackFrameList. `end_idx` can be larger than the actual number of frames,
114+
/// in which case all the frames will be fetched. Acquires the writer end of
115+
/// the list mutex.
105116
/// Returns true if the function was interrupted, false otherwise.
106-
bool GetFramesUpTo(uint32_t end_idx,
107-
InterruptionControl allow_interrupt = AllowInterruption);
108-
109-
void GetOnlyConcreteFramesUpTo(uint32_t end_idx, Unwind &unwinder);
110-
111-
void SynthesizeTailCallFrames(StackFrame &next_frame);
112-
113-
bool GetAllFramesFetched() { return m_concrete_frames_fetched == UINT32_MAX; }
117+
/// Callers should first check (under the shared mutex) whether we need to
118+
/// fetch frames or not.
119+
bool GetFramesUpTo(uint32_t end_idx, InterruptionControl allow_interrupt);
120+
121+
// This should be called with either the reader or writer end of the list
122+
// mutex held:
123+
bool GetAllFramesFetched() const {
124+
return m_concrete_frames_fetched == UINT32_MAX;
125+
}
114126

127+
// This should be called with the writer end of the list mutex held.
115128
void SetAllFramesFetched() { m_concrete_frames_fetched = UINT32_MAX; }
116129

117130
bool DecrementCurrentInlinedDepth();
@@ -122,6 +135,9 @@ class StackFrameList {
122135

123136
void SetCurrentInlinedDepth(uint32_t new_depth);
124137

138+
/// Calls into the stack frame recognizers and stop info to set the most
139+
/// relevant frame. This can call out to arbitrary user code so it can't
140+
/// hold the StackFrameList mutex.
125141
void SelectMostRelevantFrame();
126142

127143
typedef std::vector<lldb::StackFrameSP> collection;
@@ -138,11 +154,16 @@ class StackFrameList {
138154
// source of information.
139155
lldb::StackFrameListSP m_prev_frames_sp;
140156

141-
/// A mutex for this frame list.
142-
// TODO: This mutex may not always be held when required. In particular, uses
143-
// of the StackFrameList APIs in lldb_private::Thread look suspect. Consider
144-
// passing around a lock_guard reference to enforce proper locking.
145-
mutable std::recursive_mutex m_mutex;
157+
/// A mutex for this frame list. The only public API that requires the
158+
/// unique lock is Clear. All other clients take the shared lock, though
159+
/// if we need more frames we may swap shared for unique to fulfill that
160+
/// requirement.
161+
mutable std::shared_mutex m_list_mutex;
162+
163+
// Setting the inlined depth should be protected against other attempts to
164+
// change it, but since it doesn't mutate the list itself, we can limit the
165+
// critical regions it produces by having a separate mutex.
166+
mutable std::mutex m_inlined_depth_mutex;
146167

147168
/// A cache of frames. This may need to be updated when the program counter
148169
/// changes.
@@ -171,6 +192,21 @@ class StackFrameList {
171192
const bool m_show_inlined_frames;
172193

173194
private:
195+
uint32_t SetSelectedFrameNoLock(lldb_private::StackFrame *frame);
196+
lldb::StackFrameSP
197+
GetFrameAtIndexNoLock(uint32_t idx,
198+
std::shared_lock<std::shared_mutex> &guard);
199+
200+
/// These two Fetch frames APIs and SynthesizeTailCallFrames are called in
201+
/// GetFramesUpTo, they are the ones that actually add frames. They must be
202+
/// called with the writer end of the list mutex held.
203+
204+
/// Returns true if fetching frames was interrupted, false otherwise.
205+
bool FetchFramesUpTo(uint32_t end_idx, InterruptionControl allow_interrupt);
206+
/// Not currently interruptible so returns void.
207+
void FetchOnlyConcreteFramesUpTo(uint32_t end_idx);
208+
void SynthesizeTailCallFrames(StackFrame &next_frame);
209+
174210
StackFrameList(const StackFrameList &) = delete;
175211
const StackFrameList &operator=(const StackFrameList &) = delete;
176212
};

0 commit comments

Comments
 (0)