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.
92 lines
2.5 KiB
Plaintext
92 lines
2.5 KiB
Plaintext
#include "pseudo_barrier.h"
|
|
|
|
#include <atomic>
|
|
#include <thread>
|
|
|
|
%include_SB_APIs%
|
|
|
|
#include "common.h"
|
|
|
|
using namespace lldb;
|
|
|
|
void test (SBDebugger &dbg, std::vector<std::string> args) {
|
|
|
|
SBError error;
|
|
dbg.SetAsync(false);
|
|
SBTarget target = dbg.CreateTarget(args.at(0).c_str());
|
|
if (!target.IsValid())
|
|
throw Exception("Invalid target");
|
|
|
|
// Now set our breakpoint and launch:
|
|
SBFileSpec main_sourcefile("deep_stack.cpp");
|
|
SBBreakpoint bkpt = target.BreakpointCreateBySourceRegex("Set a breakpoint here",
|
|
main_sourcefile);
|
|
if (bkpt.GetNumLocations() == 0)
|
|
throw Exception("Main breakpoint got no locations");
|
|
|
|
SBLaunchInfo launch_info = target.GetLaunchInfo();
|
|
SBProcess process = target.Launch(launch_info, error);
|
|
if (error.Fail())
|
|
throw Exception("Failed to launch process");
|
|
if (!process.IsValid())
|
|
throw Exception("Process is not valid");
|
|
if (process.GetState() != lldb::eStateStopped)
|
|
throw Exception("Process was not stopped");
|
|
|
|
size_t num_threads = process.GetNumThreads();
|
|
if (num_threads != 1)
|
|
throw Exception("Unexpected number of threads.");
|
|
SBThread cur_thread = process.GetThreadAtIndex(0);
|
|
if (!cur_thread.IsValid())
|
|
throw Exception("Didn't get a valid thread");
|
|
|
|
// Record the number of frames at the point where we stopped:
|
|
const size_t num_frames = cur_thread.GetNumFrames();
|
|
// Now step once to clear the frame cache:
|
|
cur_thread.StepOver();
|
|
|
|
// Create three threads and set them to getting frames simultaneously,
|
|
// and make sure we don't deadlock.
|
|
pseudo_barrier_t rendevous;
|
|
pseudo_barrier_init(rendevous, 5);
|
|
std::atomic_size_t success(true);
|
|
std::atomic_size_t largest(0);
|
|
|
|
auto lambda = [&](size_t stride){
|
|
pseudo_barrier_wait(rendevous);
|
|
bool younger = true;
|
|
while (1) {
|
|
size_t cursor = largest;
|
|
if (cursor > stride && !younger) {
|
|
cursor -= stride;
|
|
younger = true;
|
|
} else {
|
|
cursor += stride;
|
|
largest += stride;
|
|
younger = false;
|
|
}
|
|
SBFrame frame = cur_thread.GetFrameAtIndex(cursor);
|
|
if (!frame.IsValid()) {
|
|
if (cursor < num_frames)
|
|
success = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
std::thread thread1(lambda, 1);
|
|
std::thread thread2(lambda, 3);
|
|
std::thread thread3(lambda, 5);
|
|
std::thread thread4(lambda, 7);
|
|
std::thread thread5(lambda, 11);
|
|
thread1.join();
|
|
thread2.join();
|
|
thread3.join();
|
|
thread4.join();
|
|
thread5.join();
|
|
|
|
if (!success)
|
|
throw Exception("One thread stopped before 1000");
|
|
}
|