[Lldb-commits] [lldb] fe61b38 - Add a Debugger interruption mechanism in conjunction with the

Jim Ingham via lldb-commits lldb-commits at lists.llvm.org
Wed Mar 15 16:45:24 PDT 2023


Author: Jim Ingham
Date: 2023-03-15T16:45:14-07:00
New Revision: fe61b38258bf4c5f34c32de26f4ed11ef5c32ebc

URL: https://github.com/llvm/llvm-project/commit/fe61b38258bf4c5f34c32de26f4ed11ef5c32ebc
DIFF: https://github.com/llvm/llvm-project/commit/fe61b38258bf4c5f34c32de26f4ed11ef5c32ebc.diff

LOG: Add a Debugger interruption mechanism in conjunction with the
Command Interpreter mechanism.

Differential Revision: https://reviews.llvm.org/D145136

Added: 
    lldb/test/API/python_api/was_interrupted/Makefile
    lldb/test/API/python_api/was_interrupted/TestDebuggerInterruption.py
    lldb/test/API/python_api/was_interrupted/interruptible.py
    lldb/test/API/python_api/was_interrupted/main.c

Modified: 
    lldb/include/lldb/API/SBCommandInterpreter.h
    lldb/include/lldb/API/SBDebugger.h
    lldb/include/lldb/Core/Debugger.h
    lldb/include/lldb/Interpreter/CommandInterpreter.h
    lldb/source/API/SBCommandInterpreter.cpp
    lldb/source/API/SBDebugger.cpp
    lldb/source/API/SBFrame.cpp
    lldb/source/Commands/CommandObjectTarget.cpp
    lldb/source/Commands/CommandObjectThread.cpp
    lldb/source/Core/Debugger.cpp
    lldb/source/Interpreter/CommandInterpreter.cpp
    lldb/source/Target/StackFrameList.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/API/SBCommandInterpreter.h b/lldb/include/lldb/API/SBCommandInterpreter.h
index 08ed989714d13..7c3f8275471e8 100644
--- a/lldb/include/lldb/API/SBCommandInterpreter.h
+++ b/lldb/include/lldb/API/SBCommandInterpreter.h
@@ -241,7 +241,20 @@ class SBCommandInterpreter {
                                        lldb::SBStringList &matches,
                                        lldb::SBStringList &descriptions);
 
+  /// Returns whether an interrupt flag was raised either by the SBDebugger - 
+  /// when the function is not running on the RunCommandInterpreter thread, or
+  /// by SBCommandInterpreter::InterruptCommand if it is.  If your code is doing
+  /// interruptible work, check this API periodically, and interrupt if it 
+  /// returns true.
   bool WasInterrupted() const;
+  
+  /// Interrupts the command currently executing in the RunCommandInterpreter
+  /// thread.
+  ///
+  /// \return
+  ///   \b true if there was a command in progress to recieve the interrupt.
+  ///   \b false if there's no command currently in flight.
+  bool InterruptCommand();
 
   // Catch commands before they execute by registering a callback that will get
   // called when the command gets executed. This allows GUI or command line

diff  --git a/lldb/include/lldb/API/SBDebugger.h b/lldb/include/lldb/API/SBDebugger.h
index 8e8e1b61e08fb..bdcd5eeaaffdb 100644
--- a/lldb/include/lldb/API/SBDebugger.h
+++ b/lldb/include/lldb/API/SBDebugger.h
@@ -197,6 +197,10 @@ class LLDB_API SBDebugger {
   lldb::SBCommandInterpreter GetCommandInterpreter();
 
   void HandleCommand(const char *command);
+  
+  void RequestInterrupt();
+  void CancelInterruptRequest();
+  bool InterruptRequested();
 
   lldb::SBListener GetListener();
 

diff  --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h
index 3d2a37871d794..1d9d691bc2b06 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -371,6 +371,48 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
   bool IsHandlingEvents() const { return m_event_handler_thread.IsJoinable(); }
 
   Status RunREPL(lldb::LanguageType language, const char *repl_options);
+  
+  /// Interruption in LLDB:
+  /// 
+  /// This is a voluntary interruption mechanism, not preemptive.  Parts of lldb
+  /// that do work that can be safely interrupted call 
+  /// Debugger::InterruptRequested and if that returns true, they should return
+  /// at a safe point, shortcutting the rest of the work they were to do.
+  ///  
+  /// lldb clients can both offer a CommandInterpreter (through 
+  /// RunCommandInterpreter) and use the SB API's for their own purposes, so it
+  /// is convenient to separate "interrupting the CommandInterpreter execution"
+  /// and interrupting the work it is doing with the SB API's.  So there are two 
+  /// ways to cause an interrupt: 
+  ///   * CommandInterpreter::InterruptCommand: Interrupts the command currently
+  ///     running in the command interpreter IOHandler thread
+  ///   * Debugger::RequestInterrupt: Interrupts are active on anything but the
+  ///     CommandInterpreter thread till CancelInterruptRequest is called.
+  ///
+  /// Since the two checks are mutually exclusive, however, it's also convenient
+  /// to have just one function to check the interrupt state.
+
+
+  /// Bump the "interrupt requested" count on the debugger to support
+  /// cooperative interruption.  If this is non-zero, InterruptRequested will
+  /// return true.  Interruptible operations are expected to query the
+  /// InterruptRequested API periodically, and interrupt what they were doing
+  /// if it returns \b true.
+  ///
+  void RequestInterrupt();
+  
+  /// Decrement the "interrupt requested" counter.
+  void CancelInterruptRequest();
+  
+  /// This is the correct way to query the state of Interruption.
+  /// If you are on the RunCommandInterpreter thread, it will check the 
+  /// command interpreter state, and if it is on another thread it will 
+  /// check the debugger Interrupt Request state.
+  ///
+  /// \return
+  ///  A boolean value, if \b true an interruptible operation should interrupt
+  ///  itself.
+  bool InterruptRequested();
 
   // This is for use in the command interpreter, when you either want the
   // selected target, or if no target is present you want to prime the dummy
@@ -512,13 +554,19 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
 
   bool PopIOHandler(const lldb::IOHandlerSP &reader_sp);
 
-  bool HasIOHandlerThread();
+  bool HasIOHandlerThread() const;
 
   bool StartIOHandlerThread();
 
   void StopIOHandlerThread();
+  
+  // Sets the IOHandler thread to the new_thread, and returns
+  // the previous IOHandler thread.
+  HostThread SetIOHandlerThread(HostThread &new_thread);
 
   void JoinIOHandlerThread();
+  
+  bool IsIOHandlerThreadCurrentThread() const;
 
   lldb::thread_result_t IOHandlerThread();
 
@@ -602,6 +650,9 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
   lldb_private::DebuggerDestroyCallback m_destroy_callback = nullptr;
   void *m_destroy_callback_baton = nullptr;
 
+  uint32_t m_interrupt_requested = 0; ///< Tracks interrupt requests
+  std::mutex m_interrupt_mutex;
+
   // Events for m_sync_broadcaster
   enum {
     eBroadcastBitEventThreadIsListening = (1 << 0),

diff  --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h
index ad6427330dfd4..cf44412b13026 100644
--- a/lldb/include/lldb/Interpreter/CommandInterpreter.h
+++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h
@@ -355,7 +355,7 @@ class CommandInterpreter : public Broadcaster,
                      CommandReturnObject &result,
                      bool force_repeat_command = false);
 
-  bool WasInterrupted() const;
+  bool InterruptCommand();
 
   /// Execute a list of commands in sequence.
   ///
@@ -640,6 +640,10 @@ class CommandInterpreter : public Broadcaster,
 protected:
   friend class Debugger;
 
+  // This checks just the RunCommandInterpreter interruption state.  It is only
+  // meant to be used in Debugger::InterruptRequested
+  bool WasInterrupted() const;
+
   // IOHandlerDelegate functions
   void IOHandlerInputComplete(IOHandler &io_handler,
                               std::string &line) override;
@@ -702,7 +706,6 @@ class CommandInterpreter : public Broadcaster,
 
   void StartHandlingCommand();
   void FinishHandlingCommand();
-  bool InterruptCommand();
 
   Debugger &m_debugger; // The debugger session that this interpreter is
                         // associated with

diff  --git a/lldb/source/API/SBCommandInterpreter.cpp b/lldb/source/API/SBCommandInterpreter.cpp
index 35c55283855df..36b70bc1e03a7 100644
--- a/lldb/source/API/SBCommandInterpreter.cpp
+++ b/lldb/source/API/SBCommandInterpreter.cpp
@@ -141,7 +141,13 @@ bool SBCommandInterpreter::IsActive() {
 bool SBCommandInterpreter::WasInterrupted() const {
   LLDB_INSTRUMENT_VA(this);
 
-  return (IsValid() ? m_opaque_ptr->WasInterrupted() : false);
+  return (IsValid() ? m_opaque_ptr->GetDebugger().InterruptRequested() : false);
+}
+
+bool SBCommandInterpreter::InterruptCommand() {
+  LLDB_INSTRUMENT_VA(this);
+  
+  return (IsValid() ? m_opaque_ptr->InterruptCommand() : false);
 }
 
 const char *SBCommandInterpreter::GetIOHandlerControlSequence(char ch) {

diff  --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp
index d90fcbf5031a4..2e7f06c7bb1b7 100644
--- a/lldb/source/API/SBDebugger.cpp
+++ b/lldb/source/API/SBDebugger.cpp
@@ -1699,3 +1699,24 @@ SBDebugger::LoadTraceFromFile(SBError &error,
   LLDB_INSTRUMENT_VA(this, error, trace_description_file);
   return SBTrace::LoadTraceFromFile(error, *this, trace_description_file);
 }
+
+void SBDebugger::RequestInterrupt() {
+  LLDB_INSTRUMENT_VA(this);
+  
+  if (m_opaque_sp)
+    m_opaque_sp->RequestInterrupt();  
+}
+void SBDebugger::CancelInterruptRequest()  {
+  LLDB_INSTRUMENT_VA(this);
+  
+  if (m_opaque_sp)
+    m_opaque_sp->CancelInterruptRequest();  
+}
+
+bool SBDebugger::InterruptRequested()   {
+  LLDB_INSTRUMENT_VA(this);
+  
+  if (m_opaque_sp)
+    return m_opaque_sp->InterruptRequested();
+  return false;
+}

diff  --git a/lldb/source/API/SBFrame.cpp b/lldb/source/API/SBFrame.cpp
index bf833fbb95615..4cb086ed40ab4 100644
--- a/lldb/source/API/SBFrame.cpp
+++ b/lldb/source/API/SBFrame.cpp
@@ -16,6 +16,7 @@
 
 #include "Utils.h"
 #include "lldb/Core/Address.h"
+#include "lldb/Core/Debugger.h"
 #include "lldb/Core/StreamFile.h"
 #include "lldb/Core/ValueObjectRegister.h"
 #include "lldb/Core/ValueObjectVariable.h"
@@ -806,6 +807,7 @@ SBValueList SBFrame::GetVariables(const lldb::SBVariablesOptions &options) {
     if (stop_locker.TryLock(&process->GetRunLock())) {
       frame = exe_ctx.GetFramePtr();
       if (frame) {
+        Debugger &dbg = process->GetTarget().GetDebugger();
         VariableList *variable_list = nullptr;
         Status var_error;
         variable_list = frame->GetVariableList(true, &var_error);
@@ -815,6 +817,11 @@ SBValueList SBFrame::GetVariables(const lldb::SBVariablesOptions &options) {
           const size_t num_variables = variable_list->GetSize();
           if (num_variables) {
             for (const VariableSP &variable_sp : *variable_list) {
+              if (dbg.InterruptRequested()) {
+                Log *log = GetLog(LLDBLog::Host);
+                LLDB_LOG(log, "Interrupted SBFrame::GetVariables");
+                return {};
+              }
               if (variable_sp) {
                 bool add_variable = false;
                 switch (variable_sp->GetScope()) {

diff  --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index 490a7e061563a..5874453bca23f 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -2004,7 +2004,7 @@ class CommandObjectTargetModulesDumpSymtab
             result.GetOutputStream().EOL();
             result.GetOutputStream().EOL();
           }
-          if (m_interpreter.WasInterrupted())
+          if (GetDebugger().InterruptRequested())
             break;
           num_dumped++;
           DumpModuleSymtab(m_interpreter, result.GetOutputStream(),
@@ -2031,7 +2031,7 @@ class CommandObjectTargetModulesDumpSymtab
                 result.GetOutputStream().EOL();
                 result.GetOutputStream().EOL();
               }
-              if (m_interpreter.WasInterrupted())
+              if (GetDebugger().InterruptRequested())
                 break;
               num_dumped++;
               DumpModuleSymtab(m_interpreter, result.GetOutputStream(),
@@ -2092,7 +2092,7 @@ class CommandObjectTargetModulesDumpSections
       result.GetOutputStream().Format("Dumping sections for {0} modules.\n",
                                       num_modules);
       for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) {
-        if (m_interpreter.WasInterrupted())
+        if (GetDebugger().InterruptRequested())
           break;
         num_dumped++;
         DumpModuleSections(
@@ -2110,7 +2110,7 @@ class CommandObjectTargetModulesDumpSections
             FindModulesByName(target, arg_cstr, module_list, true);
         if (num_matches > 0) {
           for (size_t i = 0; i < num_matches; ++i) {
-            if (m_interpreter.WasInterrupted())
+            if (GetDebugger().InterruptRequested())
               break;
             Module *module = module_list.GetModulePointerAtIndex(i);
             if (module) {
@@ -2224,7 +2224,7 @@ class CommandObjectTargetModulesDumpClangAST
       result.GetOutputStream().Format("Dumping clang ast for {0} modules.\n",
                                       num_modules);
       for (ModuleSP module_sp : module_list.ModulesNoLocking()) {
-        if (m_interpreter.WasInterrupted())
+        if (GetDebugger().InterruptRequested())
           break;
         if (SymbolFile *sf = module_sp->GetSymbolFile())
           sf->DumpClangAST(result.GetOutputStream());
@@ -2249,7 +2249,7 @@ class CommandObjectTargetModulesDumpClangAST
       }
 
       for (size_t i = 0; i < num_matches; ++i) {
-        if (m_interpreter.WasInterrupted())
+        if (GetDebugger().InterruptRequested())
           break;
         Module *m = module_list.GetModulePointerAtIndex(i);
         if (SymbolFile *sf = m->GetSymbolFile())
@@ -2298,7 +2298,7 @@ class CommandObjectTargetModulesDumpSymfile
       result.GetOutputStream().Format(
           "Dumping debug symbols for {0} modules.\n", num_modules);
       for (ModuleSP module_sp : target_modules.ModulesNoLocking()) {
-        if (m_interpreter.WasInterrupted())
+        if (GetDebugger().InterruptRequested())
           break;
         if (DumpModuleSymbolFile(result.GetOutputStream(), module_sp.get()))
           num_dumped++;
@@ -2314,7 +2314,7 @@ class CommandObjectTargetModulesDumpSymfile
             FindModulesByName(target, arg_cstr, module_list, true);
         if (num_matches > 0) {
           for (size_t i = 0; i < num_matches; ++i) {
-            if (m_interpreter.WasInterrupted())
+            if (GetDebugger().InterruptRequested())
               break;
             Module *module = module_list.GetModulePointerAtIndex(i);
             if (module) {
@@ -2381,7 +2381,7 @@ class CommandObjectTargetModulesDumpLineTable
         if (target_modules.GetSize() > 0) {
           uint32_t num_dumped = 0;
           for (ModuleSP module_sp : target_modules.ModulesNoLocking()) {
-            if (m_interpreter.WasInterrupted())
+            if (GetDebugger().InterruptRequested())
               break;
             if (DumpCompileUnitLineTable(
                     m_interpreter, result.GetOutputStream(), module_sp.get(),

diff  --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp
index 411f28d84a21f..f06d4bd7937f9 100644
--- a/lldb/source/Commands/CommandObjectThread.cpp
+++ b/lldb/source/Commands/CommandObjectThread.cpp
@@ -228,7 +228,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
           thread->GetIndexID());
       return false;
     }
-    if (m_options.m_extended_backtrace) {
+    if (m_options.m_extended_backtrace && !GetDebugger().InterruptRequested()) {
       DoExtendedBacktrace(thread, result);
     }
 

diff  --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index 273660db066ca..2926a29463f59 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -1230,6 +1230,29 @@ StreamSP Debugger::GetAsyncErrorStream() {
   return std::make_shared<StreamAsynchronousIO>(*this, false, GetUseColor());
 }
 
+void Debugger::RequestInterrupt() {
+  std::lock_guard<std::mutex> guard(m_interrupt_mutex);
+  m_interrupt_requested++;
+}
+
+void Debugger::CancelInterruptRequest() {
+  std::lock_guard<std::mutex> guard(m_interrupt_mutex);
+  if (m_interrupt_requested > 0)
+    m_interrupt_requested--;
+}
+
+bool Debugger::InterruptRequested() {
+  // This is the one we should call internally.  This will return true either
+  // if there's a debugger interrupt and we aren't on the IOHandler thread, 
+  // or if we are on the IOHandler thread and there's a CommandInterpreter
+  // interrupt.
+  if (!IsIOHandlerThreadCurrentThread()) {
+    std::lock_guard<std::mutex> guard(m_interrupt_mutex);
+    return m_interrupt_requested != 0;
+  }
+  return GetCommandInterpreter().WasInterrupted();
+}
+
 size_t Debugger::GetNumDebuggers() {
   if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) {
     std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr);
@@ -1981,7 +2004,15 @@ void Debugger::HandleDiagnosticEvent(const lldb::EventSP &event_sp) {
   data->Dump(stream.get());
 }
 
-bool Debugger::HasIOHandlerThread() { return m_io_handler_thread.IsJoinable(); }
+bool Debugger::HasIOHandlerThread() const {
+  return m_io_handler_thread.IsJoinable(); 
+}
+
+HostThread Debugger::SetIOHandlerThread(HostThread &new_thread) {
+  HostThread old_host = m_io_handler_thread;
+  m_io_handler_thread = new_thread;
+  return old_host;
+}
 
 bool Debugger::StartIOHandlerThread() {
   if (!m_io_handler_thread.IsJoinable()) {
@@ -2013,6 +2044,12 @@ void Debugger::JoinIOHandlerThread() {
   }
 }
 
+bool Debugger::IsIOHandlerThreadCurrentThread() const {
+  if (!HasIOHandlerThread())
+    return false;
+  return m_io_handler_thread.EqualsThread(Host::GetCurrentThread());
+}
+
 Target &Debugger::GetSelectedOrDummyTarget(bool prefer_dummy) {
   if (!prefer_dummy) {
     if (TargetSP target = m_target_list.GetSelectedTarget())

diff  --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index f42dd6cb40794..cf3fff23005bf 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -1886,8 +1886,8 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
   LLDB_LOGF(log, "Processing command: %s", command_line);
   LLDB_SCOPED_TIMERF("Processing command: %s.", command_line);
 
-  if (WasInterrupted()) {
-    result.AppendError("interrupted");
+  if (GetDebugger().InterruptRequested()) {
+    result.AppendError("... Interrupted");
     return false;
   }
 
@@ -2555,7 +2555,7 @@ void CommandInterpreter::HandleCommands(const StringList &commands,
     m_debugger.SetAsyncExecution(false);
   }
 
-  for (size_t idx = 0; idx < num_lines && !WasInterrupted(); idx++) {
+  for (size_t idx = 0; idx < num_lines; idx++) {
     const char *cmd = commands.GetStringAtIndex(idx);
     if (cmd[0] == '\0')
       continue;
@@ -3035,6 +3035,9 @@ bool CommandInterpreter::InterruptCommand() {
 }
 
 bool CommandInterpreter::WasInterrupted() const {
+  if (!m_debugger.IsIOHandlerThreadCurrentThread())
+    return false;
+
   bool was_interrupted =
       (m_command_state == CommandHandlingState::eInterrupted);
   lldbassert(!was_interrupted || m_iohandler_nesting_level > 0);
@@ -3048,7 +3051,8 @@ void CommandInterpreter::PrintCommandOutput(IOHandler &io_handler,
   lldb::StreamFileSP stream = is_stdout ? io_handler.GetOutputStreamFileSP()
                                         : io_handler.GetErrorStreamFileSP();
   // Split the output into lines and poll for interrupt requests
-  while (!str.empty() && !WasInterrupted()) {
+  bool had_output = !str.empty();
+  while (!str.empty()) {
     llvm::StringRef line;
     std::tie(line, str) = str.split('\n');
     {
@@ -3059,7 +3063,7 @@ void CommandInterpreter::PrintCommandOutput(IOHandler &io_handler,
   }
 
   std::lock_guard<std::recursive_mutex> guard(io_handler.GetOutputMutex());
-  if (!str.empty())
+  if (had_output && GetDebugger().InterruptRequested())
     stream->Printf("\n... Interrupted.\n");
   stream->Flush();
 }
@@ -3372,7 +3376,13 @@ CommandInterpreterRunResult CommandInterpreter::RunCommandInterpreter(
   if (options.GetSpawnThread()) {
     m_debugger.StartIOHandlerThread();
   } else {
+    // If the current thread is not managed by a host thread, we won't detect
+    // that this IS the CommandInterpreter IOHandler thread, so make it so:
+    HostThread new_io_handler_thread(Host::GetCurrentThread());
+    HostThread old_io_handler_thread 
+        = m_debugger.SetIOHandlerThread(new_io_handler_thread);
     m_debugger.RunIOHandlers();
+    m_debugger.SetIOHandlerThread(old_io_handler_thread);
 
     if (options.GetAutoHandleEvents())
       m_debugger.StopEventHandlerThread();

diff  --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp
index c782b506a9cb8..2610b1cd6c296 100644
--- a/lldb/source/Target/StackFrameList.cpp
+++ b/lldb/source/Target/StackFrameList.cpp
@@ -9,6 +9,7 @@
 #include "lldb/Target/StackFrameList.h"
 #include "lldb/Breakpoint/Breakpoint.h"
 #include "lldb/Breakpoint/BreakpointLocation.h"
+#include "lldb/Core/Debugger.h"
 #include "lldb/Core/SourceManager.h"
 #include "lldb/Core/StreamFile.h"
 #include "lldb/Symbol/Block.h"
@@ -470,7 +471,15 @@ void StackFrameList::GetFramesUpTo(uint32_t end_idx) {
   }
 
   StackFrameSP unwind_frame_sp;
+  Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger();
   do {
+    // Check for interruption here when building the frames - this is the
+    // expensive part, Dump later on is cheap.
+    if (dbg.InterruptRequested()) {
+      Log *log = GetLog(LLDBLog::Host);
+      LLDB_LOG(log, "Interrupted %s", __FUNCTION__);
+      break;
+    }
     uint32_t idx = m_concrete_frames_fetched++;
     lldb::addr_t pc = LLDB_INVALID_ADDRESS;
     lldb::addr_t cfa = LLDB_INVALID_ADDRESS;

diff  --git a/lldb/test/API/python_api/was_interrupted/Makefile b/lldb/test/API/python_api/was_interrupted/Makefile
new file mode 100644
index 0000000000000..695335e068c0c
--- /dev/null
+++ b/lldb/test/API/python_api/was_interrupted/Makefile
@@ -0,0 +1,4 @@
+C_SOURCES := main.c
+CFLAGS_EXTRAS := -std=c99
+
+include Makefile.rules

diff  --git a/lldb/test/API/python_api/was_interrupted/TestDebuggerInterruption.py b/lldb/test/API/python_api/was_interrupted/TestDebuggerInterruption.py
new file mode 100644
index 0000000000000..3c519a65e613b
--- /dev/null
+++ b/lldb/test/API/python_api/was_interrupted/TestDebuggerInterruption.py
@@ -0,0 +1,330 @@
+"""
+Test SBDebugger.InterruptRequested and SBCommandInterpreter.WasInterrupted.
+"""
+
+import lldb
+import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.lldbtest import *
+import threading
+import os
+
+class TestDebuggerInterruption(TestBase):
+    """This test runs a command that starts up, rendevous with the test thread
+       using threading barriers, then checks whether it has been interrupted.
+
+    The command's first argument is either 'interp' or 'debugger', to test
+    InterruptRequested and WasInterrupted respectively.
+
+    The command has two modes, interrupt and check, the former is the one that
+    waits for an interrupt.  Then latter just returns whether an interrupt was
+    requested.  We use the latter to make sure we took down the flag correctly."""
+
+    NO_DEBUG_INFO_TESTCASE = True
+
+    class CommandRunner(threading.Thread):
+        """This class is for running a command, and for making a thread to run the command on.
+           It gets passed the test it is working on behalf of, and most of the important
+           objects come from the test. """
+        def __init__(self, test):
+            super().__init__()
+            self.test = test
+
+        def rendevous(self):
+            # We smuggle out barriers and event to the runner thread using thread local data:
+            import interruptible
+            interruptible.local_data = interruptible.BarrierContainer(self.test.before_interrupt_barrier,
+                                                                   self.test.after_interrupt_barrier,
+                                                                   self.test.event)
+            
+    class DirectCommandRunner(CommandRunner):
+        """"This version runs a single command using HandleCommand."""
+        def __init__(self, test, command):
+            super().__init__(test)
+            self.command = command
+        
+        def run(self):
+            self.rendevous()
+            result = self.test.dbg.GetCommandInterpreter().HandleCommand(self.command, self.test.result)
+            if self.test.result_barrier:
+                self.test.result_barrier.wait()
+
+    class CommandInterpreterRunner(CommandRunner):
+        """This version runs the CommandInterpreter and feeds the command to it."""
+        def __init__(self, test):
+            super().__init__(test)
+
+        def run(self):
+            self.rendevous()
+
+            test = self.test
+            
+            # We will use files for debugger input and output:
+            
+            # First write down the command:
+            with open(test.getBuildArtifact(test.in_filename), "w") as f:
+                f.write(f"{test.command}\n")
+
+            # Now set the debugger's stdout & stdin to our files, and run
+            # the CommandInterpreter:
+            with open(test.out_filename, "w") as outf, open(test.in_filename, "r") as inf:
+                outsbf = lldb.SBFile(outf.fileno(), "w", False)
+                orig_outf = test.dbg.GetOutputFile()
+                error = test.dbg.SetOutputFile(outsbf)
+                test.assertSuccess(error, "Could not set outfile")
+
+                insbf = lldb.SBFile(inf.fileno(), "r", False)
+                orig_inf = test.dbg.GetOutputFile()
+                error = test.dbg.SetInputFile(insbf)
+                test.assertSuccess(error, "Could not set infile")
+                      
+                options = lldb.SBCommandInterpreterRunOptions()
+                options.SetPrintResults(True)
+                options.SetEchoCommands(False)
+                
+                test.dbg.RunCommandInterpreter(True, False, options, 0, False, False)
+                test.dbg.GetOutputFile().Flush()
+
+                error = test.dbg.SetOutputFile(orig_outf)
+                test.assertSuccess(error, "Restored outfile")
+                test.dbg.SetInputFile(orig_inf)
+                test.assertSuccess(error, "Restored infile")
+            
+    def command_setup(self, args):
+        """Insert our command, if needed.  Then set up event and barriers if needed.
+           Then return the command to run."""
+
+        self.interp = self.dbg.GetCommandInterpreter()
+        self.command_name = "interruptible_command"
+        self.cmd_result = lldb.SBCommandReturnObject()
+
+        if not "check" in args:
+            self.event = threading.Event()
+            self.result_barrier = threading.Barrier(2, timeout=10)
+            self.before_interrupt_barrier = threading.Barrier(2, timeout=10)
+            self.after_interrupt_barrier = threading.Barrier(2, timeout=10)
+        else:
+            self.event = None
+            self.result_barrier = None
+            self.before_interrupt_barrier = None
+            self.after_interrupt_barrier = None
+
+        if not self.interp.UserCommandExists(self.command_name):
+            # Make the command we're going to use - it spins calling WasInterrupted:
+            cmd_filename = "interruptible"
+            cmd_filename = os.path.join(self.getSourceDir(), "interruptible.py")
+            self.runCmd(f"command script import {cmd_filename}")
+            cmd_string = f"command script add {self.command_name} --class interruptible.WelcomeCommand"
+            self.runCmd(cmd_string)
+
+        if len(args) == 0:
+            command = self.command_name
+        else:
+            command = self.command_name + " " + args
+        return command
+    
+    def run_single_command(self, command):
+        # Now start up a thread to run the command:
+        self.result.Clear()
+        self.runner = TestDebuggerInterruption.DirectCommandRunner(self, command)
+        self.runner.start()
+
+    def start_command_interp(self):
+        self.runner = TestDebuggerInterruption.CommandInterpreterRunner(self)
+        self.runner.start()
+        
+    def check_text(self, result_text, interrupted):
+        if interrupted: 
+            self.assertIn("Command was interrupted", result_text,
+                          "Got the interrupted message")
+        else:
+            self.assertIn("Command was not interrupted", result_text,
+                          "Got the not interrupted message")
+        
+    def gather_output(self):
+        # Now wait for the interrupt to interrupt the command:
+        self.runner.join(10.0)
+        finished = not self.runner.is_alive()
+        # Don't leave the runner thread stranded if the interrupt didn't work.
+        if not finished:
+            self.event.set()
+            self.runner.join(10.0)
+
+        self.assertTrue(finished, "We did finish the command")
+        
+    def check_result(self, interrupted = True):
+        self.gather_output()
+        self.check_text(self.result.GetOutput(), interrupted)
+
+    def check_result_output(self, interrupted = True):
+        self.gather_output()
+        buffer = ""
+        # Okay, now open the file for reading, and read.
+        with open(self.out_filename, "r") as f:
+            buffer = f.read()
+
+        self.assertNotEqual(len(buffer), 0, "No command data")
+        self.check_text(buffer, interrupted)
+        
+    def debugger_interrupt_test(self, use_interrupt_requested):
+        """Test that debugger interruption interrupts a command
+           running directly through HandleCommand.
+           If use_interrupt_requested is true, we'll check that API,
+           otherwise we'll check WasInterrupted.  They should both do
+           the same thing."""
+
+        if use_interrupt_requested:
+            command = self.command_setup("debugger")
+        else:
+            command = self.command_setup("interp")
+
+        self.result = lldb.SBCommandReturnObject()
+        self.run_single_command(command)
+
+        # Okay now wait till the command has gotten started to issue the interrupt:
+        self.before_interrupt_barrier.wait()
+        # I'm going to do it twice here to test that it works as a counter:
+        self.dbg.RequestInterrupt()
+        self.dbg.RequestInterrupt()
+        
+        def cleanup():
+            self.dbg.CancelInterruptRequest()
+        self.addTearDownHook(cleanup)
+        # Okay, now set both sides going:
+        self.after_interrupt_barrier.wait()
+        
+        # Check that the command was indeed interrupted.  First rendevous
+        # after the runner thread had a chance to execute the command:
+        self.result_barrier.wait()
+        self.assertTrue(self.result.Succeeded(), "Our command succeeded")
+        result_output = self.result.GetOutput()
+        self.check_result(True)
+
+        # Do it again to make sure that the counter is counting:
+        self.dbg.CancelInterruptRequest()
+        command = self.command_setup("debugger")
+        self.run_single_command(command)
+
+        # This time we won't even get to run the command, since HandleCommand
+        # checks for the interrupt state on entry, so we don't wait on the command
+        # barriers.
+        self.result_barrier.wait()
+        
+        # Again check that we were
+        self.assertFalse(self.result.Succeeded(), "Our command was not allowed to run")
+        error_output = self.result.GetError()
+        self.assertIn("... Interrupted", error_output, "Command was cut short by interrupt")
+        
+        # Now take down the flag, and make sure that we aren't interrupted:
+        self.dbg.CancelInterruptRequest()
+
+        # Now make sure that we really did take down the flag:
+        command = self.command_setup("debugger check")
+        self.run_single_command(command)
+        result_output = self.result.GetOutput()
+        self.check_result(False)
+
+    def test_debugger_interrupt_use_dbg(self):
+        self.debugger_interrupt_test(True)
+        
+    def test_debugger_interrupt_use_interp(self):
+        self.debugger_interrupt_test(False)
+        
+    def test_interp_doesnt_interrupt_debugger(self):
+        """Test that interpreter interruption does not interrupt a command
+           running directly through HandleCommand.
+           If use_interrupt_requested is true, we'll check that API,
+           otherwise we'll check WasInterrupted.  They should both do
+           the same thing."""
+
+        command = self.command_setup("debugger poll")
+
+        self.result = lldb.SBCommandReturnObject()
+        self.run_single_command(command)
+
+        # Now raise the debugger interrupt flag.  It will also interrupt the command:
+        self.before_interrupt_barrier.wait()
+        self.dbg.GetCommandInterpreter().InterruptCommand()
+        self.after_interrupt_barrier.wait()
+        
+        # Check that the command was indeed interrupted:
+        self.result_barrier.wait()
+        self.assertTrue(self.result.Succeeded(), "Our command succeeded")
+        result_output = self.result.GetOutput()
+        self.check_result(False)
+
+
+    def interruptible_command_test(self, use_interrupt_requested):
+        """Test that interpreter interruption interrupts a command
+           running in the RunCommandInterpreter loop.
+           If use_interrupt_requested is true, we'll check that API,
+           otherwise we'll check WasInterrupted.  They should both do
+           the same thing."""
+        
+        self.out_filename = self.getBuildArtifact("output")
+        self.in_filename = self.getBuildArtifact("input")
+        # We're going to overwrite the input file, but we
+        # don't want data accumulating in the output file.
+
+        if os.path.exists(self.out_filename):
+            os.unlink(self.out_filename)
+
+        # You should be able to use either check method interchangeably:
+        if use_interrupt_requested:
+            self.command = self.command_setup("debugger") + "\n"
+        else:
+            self.command = self.command_setup("interp") + "\n"
+
+        self.start_command_interp()
+
+        # Now give the interpreter a chance to run this command up
+        # to the first barrier
+        self.before_interrupt_barrier.wait()
+        # Then issue the interrupt:
+        sent_interrupt = self.dbg.GetCommandInterpreter().InterruptCommand()
+        self.assertTrue(sent_interrupt, "Did send command interrupt.")
+        # Now give the command a chance to finish:
+        self.after_interrupt_barrier.wait()
+        
+        self.check_result_output(True)
+
+        os.unlink(self.out_filename)
+
+        # Now send the check command, and make sure the flag is now down.
+        self.command = self.command_setup("interp check") + "\n"
+        self.start_command_interp()
+        
+        self.check_result_output(False)
+
+    def test_interruptible_command_check_dbg(self):
+        self.interruptible_command_test(True)
+        
+    def test_interruptible_command_check_interp(self):
+        self.interruptible_command_test(False)
+
+    def test_debugger_doesnt_interrupt_command(self):
+        """Test that debugger interruption doesn't interrupt a command
+           running in the RunCommandInterpreter loop."""
+        
+        self.out_filename = self.getBuildArtifact("output")
+        self.in_filename = self.getBuildArtifact("input")
+        # We're going to overwrite the input file, but we
+        # don't want data accumulating in the output file.
+
+        if os.path.exists(self.out_filename):
+            os.unlink(self.out_filename)
+
+        self.command = self.command_setup("interp poll") + "\n"
+
+        self.start_command_interp()
+
+        self.before_interrupt_barrier.wait()
+        self.dbg.RequestInterrupt()
+        def cleanup():
+            self.dbg.CancelInterruptRequest()
+        self.addTearDownHook(cleanup)
+        self.after_interrupt_barrier.wait()
+
+        self.check_result_output(False)
+
+        os.unlink(self.out_filename)
+

diff  --git a/lldb/test/API/python_api/was_interrupted/interruptible.py b/lldb/test/API/python_api/was_interrupted/interruptible.py
new file mode 100644
index 0000000000000..8a6b4e96f3eee
--- /dev/null
+++ b/lldb/test/API/python_api/was_interrupted/interruptible.py
@@ -0,0 +1,90 @@
+import lldb
+import threading
+
+local_data = None
+
+class BarrierContainer(threading.local):
+    def __init__(self, before_interrupt_barrier, after_interrupt_barrier,  event):
+        self.event = event
+        self.before_interrupt_barrier = before_interrupt_barrier
+        self.after_interrupt_barrier = after_interrupt_barrier
+
+class WelcomeCommand(object):
+
+    def __init__(self, debugger, session_dict):
+        return
+
+    def get_short_help(self):
+        return "A command that waits for an interrupt before returning."
+
+    def check_was_interrupted(self, debugger, use_interpreter):
+        if use_interpreter:
+            self.was_interrupted = debugger.GetCommandInterpreter().WasInterrupted()
+        else:
+            self.was_interrupted = debugger.InterruptRequested()
+        if local_data.event:
+            self.was_canceled = local_data.event.is_set()
+
+    def __call__(self, debugger, args, exe_ctx, result):
+        """Command arguments:
+            {interp/debugger} - Whether to use SBCommandInterpreter::WasInterrupted
+                                of SBDebugger::InterruptRequested().
+            check - Don't do the rendevous, just check if an interrupt was requested.
+                    If check is not provided, we'll do the lock and then check.
+            poll  - Should we poll once after the rendevous or spin waiting for the
+                    interruption to happen.
+
+            For the interrupt cases, the command waits serially on the barriers
+            passed to it in local data, giving the test runner a chance to set the
+            interrupt.  Once the barriers are passed, it waits for the interrupt
+            or the event.
+            If it finds an interrupt, it returns "Command was interrupted". If it gets an
+            event before seeing the interrupt it returns "Command was not interrupted."
+            For the "poll" case, it waits on the rendevous, then checks once.
+            For the "check" case, it doesn't wait, but just returns whether there was
+            an interrupt in force or not."""
+            
+        if local_data == None:
+            result.SetError("local data was not set.")
+            result.SetStatus(lldb.eReturnStatusFailed)
+            return
+
+        use_interpreter = "interp" in args
+        if not use_interpreter:
+            if not "debugger" in args:
+                result.SetError("Must pass either 'interp' or 'debugger'")
+                result.SetStatus(lldb.eReturnStatusFailed)
+                return
+            
+        self.was_interrupted = False
+        self.was_canceled = False
+        
+        if "check" in args:
+            self.check_was_interrupted(debugger, use_interpreter)
+            if self.was_interrupted:
+                result.Print("Command was interrupted")
+            else:
+                result.Print("Command was not interrupted")
+        else:
+            # Wait here to rendevous in the test before it sets the interrupt.
+            local_data.before_interrupt_barrier.wait()
+            # Now the test will set the interrupt, and we can continue:
+            local_data.after_interrupt_barrier.wait()
+            
+            if "poll" in args:
+                self.check_was_interrupted(debugger, use_interpreter)
+            else:
+                while not self.was_interrupted and not self.was_canceled:
+                    self.check_was_interrupted(debugger, use_interpreter)
+
+            if self.was_interrupted:
+                result.Print("Command was interrupted")
+            else:
+                result.Print("Command was not interrupted")
+
+            if self.was_canceled:
+                result.Print("Command was canceled")
+        result.SetStatus(lldb.eReturnStatusSuccessFinishResult)
+        return True
+
+

diff  --git a/lldb/test/API/python_api/was_interrupted/main.c b/lldb/test/API/python_api/was_interrupted/main.c
new file mode 100644
index 0000000000000..74b8a5995c461
--- /dev/null
+++ b/lldb/test/API/python_api/was_interrupted/main.c
@@ -0,0 +1,11 @@
+#include <stdio.h>
+
+int global_test_var = 10;
+
+int
+main()
+{
+  int test_var = 10;
+  printf ("Set a breakpoint here: %d.\n", test_var);
+  return global_test_var;
+}


        


More information about the lldb-commits mailing list