[Lldb-commits] [lldb] [lldb] Fix deadlock between the command thread and event-handler thread in scripted frame providers (PR #208242)
Med Ismail Bennani via lldb-commits
lldb-commits at lists.llvm.org
Thu Jul 9 07:48:54 PDT 2026
https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/208242
>From cf3801d3b105a101dd6e136b59a528b6b617e770 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Wed, 8 Jul 2026 16:34:48 -0700
Subject: [PATCH 1/2] [lldb] Fix scripted frame provider cross-thread
re-entrant deadlock
GetStoppedExecutionContext unconditionally blocked acquiring the
target's API mutex. A thread already holding that mutex (e.g. a `bt`
command thread, via CommandObjectParsed's eCommandTryTargetAPILock) can
end up waiting on a StackFrameList lock held by another thread (e.g. the
debugger's event-handler thread) that is itself blocked re-acquiring the
API mutex from inside a scripted frame provider's Python code that
touches SB API -- a classic AB-BA deadlock.
Introduce Policy::Capabilities::can_reenter_target_api_mutex, pushed
around every scripted-extension callback in
ScriptedPythonInterface::Dispatch and CallStaticMethod. The point isn't
"avoid this deadlock" as a goal in itself: a thread running one of these
callbacks isn't servicing a client-facing SB API entry point, it's doing
internal work on the callback's behalf, so it doesn't need the same
locking guarantees a top-level SB API call does. Avoiding the deadlock
is a beneficial side effect of giving the thread the permissions that
match what it's actually doing.
This also required making PolicyStack::Get() out-of-line:
Dispatch/CallStaticMethod live in a header included by both liblldb and
the Python script interpreter plugin, and LLDB builds with hidden
visibility by default. An inline function's function-local static is NOT
shared across shared library boundaries, so each dylib that included
Policy.h and called an inline Get() would get its own private copy of
the thread_local stack, silently splitting a single logical per-thread
stack into two. Push/Pop calls from the plugin would then operate on a
different object than Guard's destructor's Pop (already out-of-line,
compiled into liblldb), draining liblldb's copy out from under callers
that never pushed to it and tripping the "can't pop the base policy"
assert. Making Get() out-of-line ensures every dylib resolves to the
single instance defined in Policy.cpp.
Adds a regression test for the deadlock. It's a genuine cross-thread
race (the command thread vs. the debugger's event-handler thread), so
like the sibling runlock_reentrant_deadlock/was_hit_deadlock tests, it
raises the odds of hitting it within a single invocation but can't
guarantee it. The frame provider deliberately returns dict-based
synthetic frames rather than forwarding a frame under its own index, to
keep this test isolated from the separate frame-aliasing bug fixed on
top of this commit.
Also updates PolicyTest's DumpPublicState/DumpPrivateState expectations
for the new reenter_api_mutex field in Policy::Dump().
---
lldb/include/lldb/Target/ExecutionContext.h | 12 ++-
lldb/include/lldb/Utility/Policy.h | 29 +++++-
.../Interfaces/ScriptedPythonInterface.h | 7 ++
lldb/source/Target/ExecutionContext.cpp | 5 +-
lldb/source/Utility/Policy.cpp | 12 +++
.../Makefile | 2 +
...ProviderRegisterCommandAPIMutexDeadlock.py | 89 +++++++++++++++++++
.../frame_provider.py | 30 +++++++
.../main.c | 7 ++
lldb/unittests/Utility/PolicyTest.cpp | 6 +-
10 files changed, 187 insertions(+), 12 deletions(-)
create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/Makefile
create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/TestFrameProviderRegisterCommandAPIMutexDeadlock.py
create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/frame_provider.py
create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/main.c
diff --git a/lldb/include/lldb/Target/ExecutionContext.h b/lldb/include/lldb/Target/ExecutionContext.h
index bf976f4db8c87..a3935ac23df9f 100644
--- a/lldb/include/lldb/Target/ExecutionContext.h
+++ b/lldb/include/lldb/Target/ExecutionContext.h
@@ -14,6 +14,7 @@
#include "lldb/Host/ProcessRunLock.h"
#include "lldb/Target/StackID.h"
#include "lldb/Target/SyntheticFrameProvider.h"
+#include "lldb/Utility/Policy.h"
#include "lldb/lldb-private.h"
namespace lldb_private {
@@ -561,9 +562,10 @@ class ExecutionContext {
};
/// A wrapper class representing an execution context with non-null Target
-/// and Process pointers, a locked API mutex and a locked ProcessRunLock.
-/// The locks are private by design: to unlock them, destroy the
-/// StoppedExecutionContext.
+/// and Process pointers, a locked ProcessRunLock, and (unless
+/// Policy::Capabilities::can_reenter_target_api_mutex is set for the
+/// current thread) a locked API mutex. The locks are private by design; to
+/// unlock them, destroy the StoppedExecutionContext.
struct StoppedExecutionContext : ExecutionContext {
StoppedExecutionContext(lldb::TargetSP &target_sp,
lldb::ProcessSP &process_sp,
@@ -574,7 +576,9 @@ struct StoppedExecutionContext : ExecutionContext {
: m_api_lock(std::move(api_lock)), m_stop_locker(std::move(stop_locker)) {
assert(target_sp);
assert(process_sp);
- assert(m_api_lock.owns_lock());
+ assert(
+ m_api_lock.owns_lock() ||
+ PolicyStack::Get().Current().capabilities.can_reenter_target_api_mutex);
assert(m_stop_locker.IsLocked());
SetTargetSP(target_sp);
SetProcessSP(process_sp);
diff --git a/lldb/include/lldb/Utility/Policy.h b/lldb/include/lldb/Utility/Policy.h
index c841daf47a97d..8f70767817223 100644
--- a/lldb/include/lldb/Utility/Policy.h
+++ b/lldb/include/lldb/Utility/Policy.h
@@ -50,6 +50,14 @@ struct Policy {
bool can_run_breakpoint_actions = true;
bool can_load_frame_providers = true;
bool can_run_frame_recognizers = true;
+ /// Whether SB API calls made on this thread may skip re-acquiring the
+ /// target's API mutex. Set while running a scripted extension callback
+ /// (e.g. a scripted frame provider's get_frame_at_index): this thread
+ /// isn't servicing a client-facing SB API entry point, it's doing
+ /// internal work on the callback's behalf, so it doesn't need the same
+ /// locking guarantees a top-level SB API call does -- the callback is
+ /// already running under whatever protections its caller set up.
+ bool can_reenter_target_api_mutex = false;
};
/// Why a private-state policy is being pushed. Distinguishes a PST's
@@ -75,6 +83,7 @@ struct Policy {
static Policy CreatePrivateState(
PrivateStatePurpose purpose = PrivateStatePurpose::Default);
static Policy CreatePublicStateRunningExpression();
+ static Policy CreateScriptedExtensionCall();
/// @}
void Dump(Stream &s) const;
@@ -95,10 +104,17 @@ struct Policy {
/// thread's stack when the task starts.
class PolicyStack {
public:
- static PolicyStack &Get() {
- static thread_local PolicyStack s_stack;
- return s_stack;
- }
+ /// Out-of-line so every shared library resolves to the single instance
+ /// defined in Policy.cpp (part of liblldb). LLDB builds with hidden
+ /// visibility by default, so an inline function's function-local static
+ /// is NOT shared across shared library boundaries: each dylib that
+ /// included this header and called an inline Get() would get its own
+ /// private copy of the thread_local stack, silently splitting a single
+ /// logical per-thread stack into several. Push/Pop calls emitted from
+ /// different dylibs would then operate on different objects while
+ /// Guard's destructor (already out-of-line) always pops liblldb's
+ /// instance, draining it out from under callers that never pushed to it.
+ static PolicyStack &Get();
Policy Current() const;
@@ -143,6 +159,11 @@ class PolicyStack {
return Guard();
}
+ [[nodiscard]] Guard PushScriptedExtensionCall() {
+ Push(Policy::CreateScriptedExtensionCall());
+ return Guard();
+ }
+
private:
void Push(Policy policy) { m_stack.push_back(std::move(policy)); }
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index 7d0d4cdd3c6d1..79ca76577ffa9 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -17,6 +17,7 @@
#include "lldb/Interpreter/Interfaces/ScriptedInterface.h"
#include "lldb/Utility/DataBufferHeap.h"
+#include "lldb/Utility/Policy.h"
#include "../PythonDataObjects.h"
#include "../SWIGPythonBridge.h"
@@ -415,6 +416,9 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
return ErrorWithMessage<T>(caller_signature, "missing script class name",
error);
+ PolicyStack::Guard policy_guard =
+ PolicyStack::Get().PushScriptedExtensionCall();
+
Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
Locker::FreeLock);
@@ -510,6 +514,9 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
return ErrorWithMessage<T>(caller_signature, "python object ill-formed",
error);
+ PolicyStack::Guard policy_guard =
+ PolicyStack::Get().PushScriptedExtensionCall();
+
Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
Locker::FreeLock);
diff --git a/lldb/source/Target/ExecutionContext.cpp b/lldb/source/Target/ExecutionContext.cpp
index e4b2f07d8d8d1..ec0f45cfa404d 100644
--- a/lldb/source/Target/ExecutionContext.cpp
+++ b/lldb/source/Target/ExecutionContext.cpp
@@ -145,8 +145,9 @@ lldb_private::GetStoppedExecutionContext(
return llvm::createStringError(
"StoppedExecutionContext created with a null target");
- auto api_lock =
- std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+ std::unique_lock<std::recursive_mutex> api_lock;
+ if (!PolicyStack::Get().Current().capabilities.can_reenter_target_api_mutex)
+ api_lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
auto process_sp = exe_ctx_ref_ptr->GetProcessSP();
if (!process_sp)
diff --git a/lldb/source/Utility/Policy.cpp b/lldb/source/Utility/Policy.cpp
index 3c2fa8f7fc867..19aaeb3bcadfa 100644
--- a/lldb/source/Utility/Policy.cpp
+++ b/lldb/source/Utility/Policy.cpp
@@ -15,6 +15,11 @@
using namespace lldb_private;
+PolicyStack &PolicyStack::Get() {
+ static thread_local PolicyStack s_stack;
+ return s_stack;
+}
+
Policy PolicyStack::Current() const {
Policy p = m_stack.back();
if (Log *log = GetLog(LLDBLog::Process)) {
@@ -51,6 +56,12 @@ Policy Policy::CreatePublicStateRunningExpression() {
return p;
}
+Policy Policy::CreateScriptedExtensionCall() {
+ Policy p = PolicyStack::Get().Current();
+ p.capabilities.can_reenter_target_api_mutex = true;
+ return p;
+}
+
PolicyStack::Guard::~Guard() {
if (!m_active)
return;
@@ -95,6 +106,7 @@ void Policy::Dump(Stream &s) const {
s << " bp_actions=" << capabilities.can_run_breakpoint_actions;
s << " frame_providers=" << capabilities.can_load_frame_providers;
s << " frame_recognizers=" << capabilities.can_run_frame_recognizers;
+ s << " reenter_api_mutex=" << capabilities.can_reenter_target_api_mutex;
s << '}';
}
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/Makefile b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/Makefile
new file mode 100644
index 0000000000000..c9319d6e6888a
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/Makefile
@@ -0,0 +1,2 @@
+C_SOURCES := main.c
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/TestFrameProviderRegisterCommandAPIMutexDeadlock.py b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/TestFrameProviderRegisterCommandAPIMutexDeadlock.py
new file mode 100644
index 0000000000000..09345b11cd84c
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/TestFrameProviderRegisterCommandAPIMutexDeadlock.py
@@ -0,0 +1,89 @@
+"""
+Test that a scripted frame provider whose get_frame_at_index touches SB
+API (self.input_frames) does not deadlock when running `bt` from the
+command interpreter.
+
+GetStoppedExecutionContext (used by SBFrame::IsValid, among others)
+unconditionally blocked acquiring the target's API mutex. The command
+thread running `bt` already holds that mutex (CommandObjectParsed's
+eCommandTryTargetAPILock) and can end up waiting on a StackFrameList
+lock held by the debugger's event-handler thread, which is itself
+blocked re-acquiring the API mutex from inside this provider's Python
+code -- an AB-BA deadlock between the command thread and the
+event-handler thread.
+
+The event-handler thread only runs when commands are driven through
+SBDebugger.RunCommandInterpreter (what the lldb driver itself uses),
+not through plain HandleCommand, so this test drives commands that way.
+
+Note: this is a genuine cross-thread race (the command thread vs. the
+debugger's event-handler thread), not a deterministic sequential
+deadlock, so this test is best-effort -- like the sibling
+runlock_reentrant_deadlock/was_hit_deadlock tests, it raises the odds of
+hitting the race within a single invocation but cannot guarantee it.
+"""
+
+import os
+import lldb
+import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+
+
+class TestFrameProviderRegisterCommandAPIMutexDeadlock(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ def test_register_command_then_bt_no_deadlock(self):
+ """
+ Register a scripted frame provider whose get_frame_at_index
+ touches SB API, then repeatedly run `bt` through
+ RunCommandInterpreter. Should complete without deadlocking.
+ """
+ self.build()
+
+ lldbutil.run_to_name_breakpoint(self, "frame3")
+
+ provider_path = os.path.join(self.getSourceDir(), "frame_provider.py")
+
+ commands = ["command script import " + provider_path]
+ commands.append(
+ "target frame-provider register -C frame_provider.DictFrameProvider"
+ )
+ # Run `bt` several times to raise the odds of hitting the race
+ # between the command thread and the debugger's event-handler
+ # thread within a single test invocation.
+ commands.extend(["bt"] * 20)
+ commands.append("quit")
+
+ stdin_path = self.getBuildArtifact("stdin.txt")
+ stdout_path = self.getBuildArtifact("stdout.txt")
+ with open(stdin_path, "w") as f:
+ f.write("\n".join(commands) + "\n")
+
+ with open(stdin_path, "r") as in_fileH, open(stdout_path, "w") as out_fileH:
+ in_sbf = lldb.SBFile(in_fileH.fileno(), "r", False)
+ out_sbf = lldb.SBFile(out_fileH.fileno(), "w", False)
+ self.assertSuccess(self.dbg.SetInputFile(in_sbf))
+ self.assertSuccess(self.dbg.SetOutputFile(out_sbf))
+ self.assertSuccess(self.dbg.SetErrorFile(out_sbf))
+
+ options = lldb.SBCommandInterpreterRunOptions()
+ options.SetEchoCommands(False)
+ options.SetPrintResults(True)
+ options.SetStopOnError(False)
+ options.SetStopOnCrash(False)
+
+ # If the API-mutex deadlock regresses, this call hangs forever
+ # (timing out the test run).
+ n_errors, quit_requested, has_crashed = self.dbg.RunCommandInterpreter(
+ True, False, options, 0, False, False
+ )
+
+ with open(stdout_path, "r") as out_fileH:
+ output = out_fileH.read()
+
+ self.assertFalse(has_crashed, "lldb should not have crashed")
+ self.assertTrue(quit_requested, "quit command should have been processed")
+ self.assertEqual(n_errors, 0, f"unexpected errors in output:\n{output}")
+
+ self.assertIn("successfully registered scripted frame provider", output)
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/frame_provider.py b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/frame_provider.py
new file mode 100644
index 0000000000000..e38d964cb32c2
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/frame_provider.py
@@ -0,0 +1,30 @@
+"""
+Frame provider that returns dict-based synthetic frames (never identity
+forwarding), while touching self.input_frames from get_frame_at_index.
+
+Returning a dict keeps this test isolated from the frame-aliasing bug:
+dict-based frames always go through ScriptedFrameProvider's
+create_frame_from_dict helper, which builds a brand new StackFrame and
+never reuses (or wraps via BorrowedStackFrame) the parent list's frame
+object. Only the API-mutex deadlock is reachable through this path.
+"""
+
+from lldb.plugins.scripted_frame_provider import ScriptedFrameProvider
+
+
+class DictFrameProvider(ScriptedFrameProvider):
+ @staticmethod
+ def get_description():
+ return "Provider that returns dict-based synthetic frames"
+
+ def get_frame_at_index(self, index):
+ if index >= len(self.input_frames):
+ return None
+ # __getitem__ calls SBFrame.IsValid() internally, which is what
+ # exercises GetStoppedExecutionContext.
+ frame = self.input_frames[index]
+ if frame is None:
+ return None
+ return {"idx": index, "pc": frame.GetPC()}
+
+
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/main.c b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/main.c
new file mode 100644
index 0000000000000..1aa56e3eddf7a
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/register_command_api_mutex_deadlock/main.c
@@ -0,0 +1,7 @@
+int frame3() { return 3; }
+
+int frame2() { return frame3(); }
+
+int frame1() { return frame2(); }
+
+int main() { return frame1(); }
diff --git a/lldb/unittests/Utility/PolicyTest.cpp b/lldb/unittests/Utility/PolicyTest.cpp
index 5ad045a03d30b..f98989ab4e3eb 100644
--- a/lldb/unittests/Utility/PolicyTest.cpp
+++ b/lldb/unittests/Utility/PolicyTest.cpp
@@ -145,7 +145,8 @@ TEST(PolicyTest, DumpPublicState) {
EXPECT_EQ(s.GetString(),
"policy: view=public, capabilities={"
"eval_expr=true run_all=true try_all=true "
- "bp_actions=true frame_providers=true frame_recognizers=true}");
+ "bp_actions=true frame_providers=true frame_recognizers=true "
+ "reenter_api_mutex=false}");
}
TEST(PolicyTest, DumpPrivateState) {
@@ -154,7 +155,8 @@ TEST(PolicyTest, DumpPrivateState) {
EXPECT_EQ(s.GetString(),
"policy: view=private, capabilities={"
"eval_expr=true run_all=true try_all=true "
- "bp_actions=true frame_providers=true frame_recognizers=true}");
+ "bp_actions=true frame_providers=true frame_recognizers=true "
+ "reenter_api_mutex=false}");
}
TEST(PolicyTest, DumpStack) {
>From a0076675087506826ed40242200c5f6a05dab308 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Wed, 8 Jul 2026 16:42:22 -0700
Subject: [PATCH 2/2] [lldb] Add Target::LockAPIMutex() and convert command
re-entrancy sites
Encapsulate the can_reenter_target_api_mutex policy check in a new
Target::LockAPIMutex(), next to GetAPIMutex() (which already picks
between the public/private mutex based on the policy's view), instead
of leaving free-standing `PolicyStack::Get()...` checks at call sites.
It returns a real lock normally, or a no-op unlocked one when the
current thread's policy says it doesn't need one.
Convert GetStoppedExecutionContext and every other site that
unconditionally locks the target's API mutex specifically to guard
against command/callback re-entrancy -- CommandObject's
eCommandTryTargetAPILock, SBDebugger::HandleCommand,
SBDebugger::HandleProcessEvent, SBCommandInterpreter::GetProcess, and
its three SourceInitFileIn*Directory overloads -- to go through
LockAPIMutex() instead. These mirror the exact shape of the deadlock
fixed in the parent commit: locking around dispatching another command
or interpreter action, the same re-entrant path a scripted extension
callback can hit.
There are ~150 more GetAPIMutex() call sites across SBTarget.cpp,
SBProcess.cpp, SBBreakpoint*.cpp, SBWatchpoint.cpp, SBValue.cpp, etc.
that lock unconditionally and could theoretically deadlock the same way
if invoked from inside a scripted extension callback. Converting those
is a separate, larger follow-up, not folded into this commit.
---
lldb/include/lldb/Target/Target.h | 9 +++++++++
lldb/source/API/SBCommandInterpreter.cpp | 8 ++++----
lldb/source/API/SBDebugger.cpp | 4 ++--
lldb/source/Interpreter/CommandObject.cpp | 3 +--
lldb/source/Target/ExecutionContext.cpp | 4 +---
lldb/source/Target/Target.cpp | 8 ++++++++
6 files changed, 25 insertions(+), 11 deletions(-)
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 31e2cd0ae5018..19f09054b3f79 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -763,6 +763,15 @@ class Target : public std::enable_shared_from_this<Target>,
std::recursive_mutex &GetAPIMutex();
+ /// Lock the API mutex for the current policy's view, unless the current
+ /// thread's policy says it doesn't need to (see
+ /// Policy::Capabilities::can_reenter_target_api_mutex): callers running on
+ /// behalf of a scripted extension callback aren't servicing a top-level
+ /// SB API entry point themselves, so they don't need the mutex a
+ /// top-level call already holds on their behalf. Returns an unlocked
+ /// lock in that case.
+ std::unique_lock<std::recursive_mutex> LockAPIMutex();
+
void DeleteCurrentProcess();
void CleanupProcess();
diff --git a/lldb/source/API/SBCommandInterpreter.cpp b/lldb/source/API/SBCommandInterpreter.cpp
index ae6b3d3418655..006cb3452835e 100644
--- a/lldb/source/API/SBCommandInterpreter.cpp
+++ b/lldb/source/API/SBCommandInterpreter.cpp
@@ -385,7 +385,7 @@ SBProcess SBCommandInterpreter::GetProcess() {
if (IsValid()) {
TargetSP target_sp(m_opaque_ptr->GetSelectedTarget());
if (target_sp) {
- std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+ std::unique_lock<std::recursive_mutex> guard = target_sp->LockAPIMutex();
process_sp = target_sp->GetProcessSP();
sb_process.SetSP(process_sp);
}
@@ -474,7 +474,7 @@ void SBCommandInterpreter::SourceInitFileInGlobalDirectory(
TargetSP target_sp(m_opaque_ptr->GetSelectedTarget());
std::unique_lock<std::recursive_mutex> lock;
if (target_sp)
- lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+ lock = target_sp->LockAPIMutex();
m_opaque_ptr->SourceInitFileGlobal(result.ref());
} else {
result->AppendError("SBCommandInterpreter is not valid");
@@ -497,7 +497,7 @@ void SBCommandInterpreter::SourceInitFileInHomeDirectory(
TargetSP target_sp(m_opaque_ptr->GetSelectedTarget());
std::unique_lock<std::recursive_mutex> lock;
if (target_sp)
- lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+ lock = target_sp->LockAPIMutex();
m_opaque_ptr->SourceInitFileHome(result.ref(), is_repl);
} else {
result->AppendError("SBCommandInterpreter is not valid");
@@ -513,7 +513,7 @@ void SBCommandInterpreter::SourceInitFileInCurrentWorkingDirectory(
TargetSP target_sp(m_opaque_ptr->GetSelectedTarget());
std::unique_lock<std::recursive_mutex> lock;
if (target_sp)
- lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+ lock = target_sp->LockAPIMutex();
m_opaque_ptr->SourceInitFileCwd(result.ref());
} else {
result->AppendError("SBCommandInterpreter is not valid");
diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp
index 95c4f761c9963..7d56e86f9d5ad 100644
--- a/lldb/source/API/SBDebugger.cpp
+++ b/lldb/source/API/SBDebugger.cpp
@@ -533,7 +533,7 @@ void SBDebugger::HandleCommand(const char *command) {
m_opaque_sp->GetCommandInterpreter().GetSelectedTarget());
std::unique_lock<std::recursive_mutex> lock;
if (target_sp)
- lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+ lock = target_sp->LockAPIMutex();
SBCommandInterpreter sb_interpreter(GetCommandInterpreter());
SBCommandReturnObject result;
@@ -606,7 +606,7 @@ void SBDebugger::HandleProcessEvent(const SBProcess &process,
char stdio_buffer[1024];
size_t len;
- std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
+ std::unique_lock<std::recursive_mutex> guard = target_sp->LockAPIMutex();
if (event_type &
(Process::eBroadcastBitSTDOUT | Process::eBroadcastBitStateChanged)) {
diff --git a/lldb/source/Interpreter/CommandObject.cpp b/lldb/source/Interpreter/CommandObject.cpp
index 75abf49e77207..715361c5c1e02 100644
--- a/lldb/source/Interpreter/CommandObject.cpp
+++ b/lldb/source/Interpreter/CommandObject.cpp
@@ -235,8 +235,7 @@ bool CommandObject::CheckRequirements(CommandReturnObject &result) {
if (flags & eCommandTryTargetAPILock) {
if (target && !target->IsDummyTarget())
- m_api_locker =
- std::unique_lock<std::recursive_mutex>(target->GetAPIMutex());
+ m_api_locker = target->LockAPIMutex();
}
}
diff --git a/lldb/source/Target/ExecutionContext.cpp b/lldb/source/Target/ExecutionContext.cpp
index ec0f45cfa404d..98636c2d42ba0 100644
--- a/lldb/source/Target/ExecutionContext.cpp
+++ b/lldb/source/Target/ExecutionContext.cpp
@@ -145,9 +145,7 @@ lldb_private::GetStoppedExecutionContext(
return llvm::createStringError(
"StoppedExecutionContext created with a null target");
- std::unique_lock<std::recursive_mutex> api_lock;
- if (!PolicyStack::Get().Current().capabilities.can_reenter_target_api_mutex)
- api_lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
+ std::unique_lock<std::recursive_mutex> api_lock = target_sp->LockAPIMutex();
auto process_sp = exe_ctx_ref_ptr->GetProcessSP();
if (!process_sp)
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index d1d07b22e382c..d38015441602d 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -5999,6 +5999,14 @@ std::recursive_mutex &Target::GetAPIMutex() {
return m_mutex;
}
+std::unique_lock<std::recursive_mutex> Target::LockAPIMutex() {
+ Policy policy = PolicyStack::Get().Current();
+ if (policy.capabilities.can_reenter_target_api_mutex)
+ return {};
+
+ return std::unique_lock<std::recursive_mutex>(GetAPIMutex());
+}
+
/// Get metrics associated with this target in JSON format.
llvm::json::Value
Target::ReportStatistics(const lldb_private::StatisticsOptions &options) {
More information about the lldb-commits
mailing list