Skip to content

Conversation

Nerixyz
Copy link
Contributor

@Nerixyz Nerixyz commented Sep 17, 2025

When quitting LLDB on Windows while a process was still running, LLDB would take unusually long to exit. This was due to a temporary deadlock:

The main thread was destroying the processes. In doing so, it iterated over the target list:

for (TargetSP target_sp : m_target_list.Targets()) {
if (target_sp) {
if (ProcessSP process_sp = target_sp->GetProcessSP())
process_sp->Finalize(false /* not destructing */);

This locks the list for the whole iteration. Finalizing the process would eventually lead to DebuggerThread::StopDebugging, which terminates the process and waits for it to exit:

DWORD wait_result = WaitForSingleObject(m_debugging_ended_event, 5000);

The debugger loop (on a separate thread) would see that the process exited and call ProcessWindows::OnExitProcess. This calls the static function Process::SetProcessExitStatus. This tries to find the process by its ID from the debugger's target list. Doing so requires locking the list, so the debugger thread would then be stuck on
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);

After 5s, the main thread would give up waiting. So every exit where the process was still running would be delayed by about 5s.

Since ProcessWindows would find itself when calling SetProcessExitStatus, we can call SetExitStatus directly.

This can also make some tests run faster. For example, the DIA PDB tests previously took 15s to run on my PC (24 jobs) and now take 5s. For all shell tests, the difference isn't that big (only about 3s), because most don't run into this and the tests run in parallel.

@llvmbot
Copy link
Member

llvmbot commented Sep 17, 2025

@llvm/pr-subscribers-lldb

Author: nerix (Nerixyz)

Changes

When quitting LLDB on Windows while a process was still running, LLDB would take unusually long to exit. This was due to a temporary deadlock:

The main thread was destroying the processes. In doing so, it iterated over the target list:

for (TargetSP target_sp : m_target_list.Targets()) {
if (target_sp) {
if (ProcessSP process_sp = target_sp->GetProcessSP())
process_sp->Finalize(false /* not destructing */);

This locks the list for the whole iteration. Finalizing the process would eventually lead to DebuggerThread::StopDebugging, which terminates the process and waits for it to exit:

DWORD wait_result = WaitForSingleObject(m_debugging_ended_event, 5000);

The debugger loop (on a separate thread) would see that the process exited and call ProcessWindows::OnExitProcess. This calls the static function Process::SetProcessExitStatus. This tries to find the process by its ID from the debugger's target list. Doing so requires locking the list, so the debugger thread would then be stuck on
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);

After 5s, the main thread would give up waiting. So every exit where the process was still running would be delayed by about 5s.

Since ProcessWindows would find itself when calling SetProcessExitStatus, we can call SetExitStatus directly.

This can also make some tests run faster. For example, the DIA PDB tests previously took 15s to run on my PC (24 jobs) and now take 5s. For all shell tests, the difference isn't that big (only about 3s), because most don't run into this and the tests run in parallel.


Full diff: https://github.com/llvm/llvm-project/pull/159308.diff

1 Files Affected:

  • (modified) lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp (+1-1)
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 27530f032ce51..0fecefe23b88e 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -666,7 +666,7 @@ void ProcessWindows::OnExitProcess(uint32_t exit_code) {
     target->ModulesDidUnload(unloaded_modules, true);
   }
 
-  SetProcessExitStatus(GetID(), true, 0, exit_code);
+  SetExitStatus(exit_code, /*exit_string=*/"");
   SetPrivateState(eStateExited);
 
   ProcessDebugger::OnExitProcess(exit_code);

Copy link
Collaborator

@DavidSpickett DavidSpickett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great find! I had noticed that lldb was slow to quit but I always thought it would be something inherent to how things work on Windows.

@mstorsjo
Copy link
Member

Nice, thanks! I've also observed this from time to time, but haven't had time to dig into it!

@Nerixyz Nerixyz merged commit a868f28 into llvm:main Sep 18, 2025
11 checks passed
@mstorsjo
Copy link
Member

mstorsjo commented Oct 1, 2025

Do you think it'd make sense to backport this fix to 21.x?

@Nerixyz
Copy link
Contributor Author

Nerixyz commented Oct 1, 2025

Do you think it'd make sense to backport this fix to 21.x?

That could make sense, and I'm fine with doing this, though, the bug was already in 20.x. Maybe @DavidSpickett has some thoughts on this?

@DavidSpickett
Copy link
Collaborator

It always "worked", just slowly. So it's like backporting a code generation improvement, which we have been known to do. Worth a try.

I would support it on the basis that the patch is very simple and we haven't seen any regressions.

@Nerixyz
Copy link
Contributor Author

Nerixyz commented Oct 1, 2025

/cherry-pick a868f28

@llvmbot
Copy link
Member

llvmbot commented Oct 1, 2025

/pull-request #161541

@llvmbot llvmbot moved this from Needs Triage to Done in LLVM Release Status Oct 1, 2025
@mstorsjo
Copy link
Member

mstorsjo commented Oct 1, 2025

Yeah - it doesn't need to be a regression in 21.x for the fix to be backported, any reasonable bug fix can be eligible IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Development

Successfully merging this pull request may close these issues.

4 participants