Skip to content

Commit ba22857

Browse files
committed
[lldb-dap] Always stop on enrty for attaching
Recently upon debugging a program with thousands of threads, lldb-dap would hang at a `threads` request sent right after receiving the `configurationDone` response. Soon after it will end the debug session with "Process <pid> ex ited with status = -1 (0xffffffff) lost connection". This is because LLDB is still in the middle of resuming all the threads. And requesting threads will require stopp ing the process. From the gdb-remote log it ended up getting `lldb::StateType::eStateInvalid` and just exit with s tatus -1. I don't think it's reasonable to allow getting threads from a running process. The alternative will be reject the `threads` request if the process is not stopped.
1 parent ae5306f commit ba22857

File tree

2 files changed

+107
-3
lines changed

2 files changed

+107
-3
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#include <condition_variable>
2+
#include <functional>
3+
#include <iostream>
4+
#include <mutex>
5+
#include <queue>
6+
#include <thread>
7+
8+
int plus_one(int n) {
9+
std::cout << "In plus_one" << std::endl;
10+
return n + 1;
11+
}
12+
13+
class ThreadPool {
14+
public:
15+
ThreadPool(size_t num_threads = std::thread::hardware_concurrency()) {
16+
for (uint32_t i = 0; i < num_threads; ++i) {
17+
threads.emplace_back(std::thread(&ThreadPool::ThreadLoop, this));
18+
}
19+
}
20+
~ThreadPool() {
21+
{
22+
std::unique_lock<std::mutex> lock(queue_mutex);
23+
terminated = true;
24+
}
25+
condition.notify_all();
26+
for (std::thread& thread : threads) {
27+
thread.join();
28+
}
29+
}
30+
void enqueue(const std::function<void()>& job) {
31+
{
32+
std::unique_lock<std::mutex> lock(queue_mutex);
33+
if (terminated)
34+
throw std::runtime_error("enqueue on stopped ThreadPool");
35+
jobs.push(job);
36+
}
37+
condition.notify_one();
38+
}
39+
40+
private:
41+
void ThreadLoop() {
42+
while (true) {
43+
std::function<void()> job;
44+
{
45+
std::unique_lock<std::mutex> lock(queue_mutex);
46+
condition.wait(lock, [this] { return !jobs.empty() || terminated; });
47+
if (terminated && jobs.empty())
48+
return;
49+
job = jobs.front();
50+
jobs.pop();
51+
}
52+
job();
53+
}
54+
}
55+
56+
bool terminated = false;
57+
std::mutex queue_mutex;
58+
std::condition_variable condition;
59+
std::vector<std::thread> threads;
60+
std::queue<std::function<void()>> jobs;
61+
};
62+
63+
void thread_func(int job_index) {
64+
std::cout << __FUNCTION__ << " (job index = " << job_index
65+
<< ") running on thread " << std::this_thread::get_id() << "\n";
66+
std::cout << "Calling function plus_one(int)\nResult = "
67+
<< plus_one(job_index) << "\n";
68+
}
69+
70+
void thread_sleep(int job_index, int sleep_sec) {
71+
std::cout << __FUNCTION__ << " (job index = " << job_index
72+
<< ") starting on thread " << std::this_thread::get_id() << "\n";
73+
std::this_thread::sleep_for(std::chrono::seconds(sleep_sec));
74+
std::cout << __FUNCTION__ << " (job index = " << job_index
75+
<< ") finished on thread " << std::this_thread::get_id() << "\n";
76+
}
77+
78+
int main() {
79+
ThreadPool tp1(2000);
80+
ThreadPool tp2;
81+
std::cout << "main() running on thread " << std::this_thread::get_id()
82+
<< "\n";
83+
std::cout
84+
<< "Program is expecting stdin. Please attach debugger and hit enter to continue\n";
85+
std::cin.get();
86+
// At least one of the thread in tp1 will be sceduled with a task twice or
87+
// more if num_jobs is larger than #threads in tp1.
88+
int num_jobs = 3000;
89+
for (int i = 0; i < num_jobs; ++i) {
90+
tp1.enqueue(std::bind(thread_func, i));
91+
}
92+
// We may or may not hit the breakpoint thread_sleep here, as the tread pool
93+
// might finish executing before debugger release the bp for tp1.
94+
// To make sure we hit the bp, we can increase the sleep time in the call.
95+
for (int i = 0; i < num_jobs; ++i) {
96+
tp2.enqueue([i] { thread_sleep(i, 1); });
97+
}
98+
for (int i = 0; i < num_jobs; ++i) {
99+
tp1.enqueue(std::bind(thread_sleep, i, 1));
100+
return 0;
101+
}
102+
}

lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,11 @@ void AttachRequestHandler::operator()(const llvm::json::Object &request) const {
7373
llvm::StringRef core_file = GetString(arguments, "coreFile").value_or("");
7474
const uint64_t timeout_seconds =
7575
GetInteger<uint64_t>(arguments, "timeout").value_or(30);
76-
dap.stop_at_entry = core_file.empty()
77-
? GetBoolean(arguments, "stopOnEntry").value_or(false)
78-
: true;
76+
// Clients like VS Code sends threads request right after receiving
77+
// configurationDone reponse where the process might be resuming.
78+
// Getting threads list on a running process is not supported by LLDB.
79+
// Always stop the process after attaching.
80+
dap.stop_at_entry = true;
7981
dap.configuration.postRunCommands = GetStrings(arguments, "postRunCommands");
8082
const llvm::StringRef debuggerRoot =
8183
GetString(arguments, "debuggerRoot").value_or("");

0 commit comments

Comments
 (0)