Skip to content

Conversation

@Jlalond
Copy link
Contributor

@Jlalond Jlalond commented Nov 17, 2025

When the target is being created, the target list acquires the mutex for the duration of the target creation process. However if a module callback is enabled and is being called in parallel there exists an opportunity to deadlock if the callback calls into targetlist. I've created a minimum repro here.

command script import dead-lock-example (from above gist)
...
target create a.out
[hangs]

This looks like a straight forward fix, where CreateTargetInternal doesn't access any state directly, and instead calls methods which they themselves are thread-safe. So I've moved the lock to when we update the list with the created target. I'm not sure if this is a comprehensive fix, but it does fix my above example and in my (albeit limited) testing, doesn't cause any strange change in behavior.

@llvmbot
Copy link
Member

llvmbot commented Nov 17, 2025

@llvm/pr-subscribers-lldb

Author: Jacob Lalonde (Jlalond)

Changes

When the target is being created, the target list acquires the mutex for the duration of the target creation process. However if a module callback is enabled and is being called in parallel there exists an opportunity to deadlock if the callback calls into targetlist. I've created a minimum repro here.

This looks like a straight forward fix, where CreateTargetInternal doesn't access any state directly, and instead calls methods which they themselves are thread-safe. So I've moved the lock to when we update the list with the created target. I'm not sure if this is a comprehensive fix, but it does fix my above example and in my (albeit limited) testing, doesn't cause any strange change in behavior.


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

1 Files Affected:

  • (modified) lldb/source/Target/TargetList.cpp (+12-2)
diff --git a/lldb/source/Target/TargetList.cpp b/lldb/source/Target/TargetList.cpp
index 2e03bc1e38ea0..af0d24d7b1d0a 100644
--- a/lldb/source/Target/TargetList.cpp
+++ b/lldb/source/Target/TargetList.cpp
@@ -48,11 +48,16 @@ Status TargetList::CreateTarget(Debugger &debugger,
                                 LoadDependentFiles load_dependent_files,
                                 const OptionGroupPlatform *platform_options,
                                 TargetSP &target_sp) {
-  std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
+  // Create Target Internal does not modify and state
+  // directly and instead calls into methods which
+  // they themselves are thread-safe. We do this so
+  // the load module call back doesn't cause a re-entry
+  // dead-lock when creating the target.
   auto result = TargetList::CreateTargetInternal(
       debugger, user_exe_path, triple_str, load_dependent_files,
       platform_options, target_sp);
 
+  std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
   if (target_sp && result.Success())
     AddTargetInternal(target_sp, /*do_select*/ true);
   return result;
@@ -63,11 +68,16 @@ Status TargetList::CreateTarget(Debugger &debugger,
                                 const ArchSpec &specified_arch,
                                 LoadDependentFiles load_dependent_files,
                                 PlatformSP &platform_sp, TargetSP &target_sp) {
-  std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
+  // Create Target Internal does not modify and state
+  // directly and instead calls into methods which
+  // they themselves are thread-safe. We do this so
+  // the load module call back doesn't cause a re-entry
+  // dead-lock when creating the target.
   auto result = TargetList::CreateTargetInternal(
       debugger, user_exe_path, specified_arch, load_dependent_files,
       platform_sp, target_sp);
 
+  std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
   if (target_sp && result.Success())
     AddTargetInternal(target_sp, /*do_select*/ true);
   return result;

@github-actions
Copy link

github-actions bot commented Nov 17, 2025

🐧 Linux x64 Test Results

  • 33126 tests passed
  • 494 tests skipped

@Jlalond Jlalond force-pushed the module_callback_deadlock branch from 8bda2cf to 6c12e75 Compare November 17, 2025 19:18
… for the entire target creation and causing a deadlock with the module callback when running in parallel.
@Jlalond Jlalond force-pushed the module_callback_deadlock branch from 6c12e75 to f2d3ad3 Compare November 17, 2025 19:40
Copy link
Collaborator

@clayborg clayborg left a comment

Choose a reason for hiding this comment

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

Seems ok to to. m_target_list_mutex is really there to protect the target list itself. We might want to move the mutex lock into TargetList::AddTargetInternal to keep things cleaner?

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants