jingham created this revision.
jingham added reviewers: labath, JDevlieghere, clayborg, jasonmolenda, 
bulbazord.
Herald added a project: All.
jingham requested review of this revision.
Herald added a project: LLDB.
Herald added a subscriber: lldb-commits.

I put up an RFC for debugger interruption a while back.  This is my version of 
that RFC.

The implementation is based on the way lldb is often used in UI's (as opposed 
to the simple use in lldb's command driver).  In those cases, the lldb client 
often includes a thread running the LLDB command interpreter so that they can 
provide an "LLDB Console" window where the user can run commands, and then on 
other threads they call various SB API's that are used to fill the Threads, 
Locals, etc. windows.

So it seemed the most useful model was to separate interrupting into 
"Interrupting commands servicing the Console" and "Interrupting work the client 
is doing on its own behalf."  For instance if the client is filling the Locals 
view, but then the user issues "continue" in the console, the client would want 
to interrupt filling the Locals view (e.g. interrupt SBFrame.FindVariables()) 
so that it can continue right away, but not interrupt the user's "continue" 
command.  Similarly, if the user types some command that's producing lots of 
output, they want to interrupt that but not stop whatever work the client was 
doing at the same time.

Another question is how do we tell "Console" window commands from Client 
commands?  Since we provide a nice API that packages up the "run a command 
interpreter" behavior, it seemed reasonable to have commands run by the thread 
servicing RunCommandInterpreter be the "Interpreter" commands, and all other 
threads belong to the client.

This is also convenient because we can programmatically determine which flag we 
should check when we check the interruption, which allows us to provide a 
unified API for checking whether an interrupt was requested.

Also, the CommandInterpreter does its job Command by Command, so there's a 
natural boundary on which to take down the interrupt request.  However the 
client program is really the only agent that can know when it wants to start & 
stop interruption, so instead of a single InterruptCommand with automatic 
boundaries, I added explicit raise & lower interrupt flag API's.

One slight ugliness is that we already have 
SBCommandInterpreter::WasInterrupted().  I could have just left that as the one 
interface to query for interruption.  But long term, I think it makes more 
sense to think about this as the Debugger figuring out who to interrupt, so I 
think an SBDebugger::InterruptRequested API is better.  Even though I can't 
remove the old API, I still added the new one.

One other detail you will see in the tests.  The current behavior for command 
interpreter interruption is that if a command is interrupted, we discard the 
output it has accumulated so far and just return the result "Interrupted..."  
That's why I had to set the ImmediateOutput file in the test.  I'm on the fence 
about this behavior, so I opted not to change it for now.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D145136

Files:
  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/Commands/CommandObjectTarget.cpp
  lldb/source/Core/Debugger.cpp
  lldb/source/Interpreter/CommandInterpreter.cpp
  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

Index: lldb/test/API/python_api/was_interrupted/main.c
===================================================================
--- /dev/null
+++ 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;
+}
Index: lldb/test/API/python_api/was_interrupted/interruptible.py
===================================================================
--- /dev/null
+++ lldb/test/API/python_api/was_interrupted/interruptible.py
@@ -0,0 +1,93 @@
+import lldb
+import threading
+
+local_data = None
+
+class LockContainer(threading.local):
+    def __init__(self, lock, event):
+        self.lock = lock
+        self.event = event
+
+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 locking, 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 lock or spin waiting for the
+                    interruption to happen.
+
+            For the interrupt cases, the command fetches a lock and event source 
+            that have been set in the local data.  It locks the lock (giving the
+            runner time to set the interrupt flag.  Then waits for the interrupt.
+            if it finds it, 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 lock, then checks once.
+            For the "check" case, it doesn't wait, but just returns whether there was
+            an interrupt in force or not."""
+            
+        # The current behavior of CommandInterpreter::PrintCommandOutput is
+        # to not print the output of an interrupted command.  I need that output
+        # so I force it to come out synchronously here:
+        if "immediate" in args:
+            result.SetImmediateOutputFile(debugger.GetOutputFile())
+            result.SetImmediateErrorFile(debugger.GetErrorFile())
+
+        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:
+            lock = local_data.lock
+            lock.release()
+            
+            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
+
+
Index: lldb/test/API/python_api/was_interrupted/TestDebuggerInterruption.py
===================================================================
--- /dev/null
+++ lldb/test/API/python_api/was_interrupted/TestDebuggerInterruption.py
@@ -0,0 +1,332 @@
+"""
+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 blocks on a lock, and once it gets
+    the lock, it spins calling WasInterrupted.
+    We use the lock to coordinate this execution with a controller thread,
+    so it can know the command is executing before trying to interrupt it.
+    We also use a barrier to coordinate the whole command runner with the
+    controlling thread, and the command also waits on an event, which we
+    use to kick it out of its loop in case of error, making the test
+    more resilient in case of errors.
+    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 lock and event to the runner thread using thread local data:
+            import interruptible
+            interruptible.local_data = interruptible.LockContainer(self.test.lock, self.test.event)
+            # If we are going to run in a locking mode, wait for the barrier so everything is set
+            # up on the other side:
+            if self.test.lock:
+                self.test.lock.acquire()
+                self.test.barrier.wait()
+
+            
+    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()
+            self.test.dbg.GetCommandInterpreter().HandleCommand(self.command, self.test.result)
+            
+            if self.test.lock:
+                self.test.lock.release()
+
+    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 the locking 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.lock = threading.Lock()
+            self.event = threading.Event()
+            self.barrier = threading.Barrier(2, timeout=10)
+        else:
+            self.lock = None
+            self.event = None
+            self.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.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)
+
+        # Now raise the debugger interrupt flag.  It will also interrupt the command:
+        self.barrier.wait()
+        self.lock.acquire()
+        self.dbg.RequestInterrupt()
+        def cleanup():
+            self.dbg.CancelInterruptRequest()
+        self.addTearDownHook(cleanup)
+        
+        # Check that the command was indeed interrupted:
+        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 flag has stayed up:
+        command = self.command_setup("debugger")
+        self.run_single_command(command)
+
+        # Now raise the debugger interrupt flag.  It will also interrupt the command:
+        self.barrier.wait()
+        self.lock.acquire()
+        
+        # Again check that we were indeed interrupted:
+        self.assertTrue(self.result.Succeeded(), "Our command succeeded")
+        result_output = self.result.GetOutput()
+        self.check_result(True)
+        
+        # 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)
+
+        self.dbg.GetCommandInterpreter().InterruptCommand()
+
+        # Now raise the debugger interrupt flag.  It will also interrupt the command:
+        self.barrier.wait()
+        self.lock.acquire()
+        
+        # Check that the command was indeed interrupted:
+        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 immediate") + "\n"
+        else:
+            self.command = self.command_setup("interp immediate") + "\n"
+
+        self.start_command_interp()
+
+        self.barrier.wait()
+        
+        # Now give the interpreter a chance to run this command up
+        # to the lock point
+        self.lock.acquire()
+
+        sent_interrupt = self.dbg.GetCommandInterpreter().InterruptCommand()
+        self.assertTrue(sent_interrupt, "Did send command interrupt.")
+        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 immediate poll") + "\n"
+
+        self.start_command_interp()
+
+        self.barrier.wait()
+        
+        self.dbg.RequestInterrupt()
+        def cleanup():
+            self.dbg.CancelInterruptRequest()
+        self.addTearDownHook(cleanup)
+        # Now give the interpreter a chance to run this command up
+        # to the lock point
+        self.lock.acquire()
+
+        self.check_result_output(False)
+
+        os.unlink(self.out_filename)
+
Index: lldb/test/API/python_api/was_interrupted/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/python_api/was_interrupted/Makefile
@@ -0,0 +1,4 @@
+C_SOURCES := main.c
+CFLAGS_EXTRAS := -std=c99
+
+include Makefile.rules
Index: lldb/source/Interpreter/CommandInterpreter.cpp
===================================================================
--- lldb/source/Interpreter/CommandInterpreter.cpp
+++ lldb/source/Interpreter/CommandInterpreter.cpp
@@ -3022,6 +3022,9 @@
 }
 
 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);
@@ -3359,7 +3362,13 @@
   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();
Index: lldb/source/Core/Debugger.cpp
===================================================================
--- lldb/source/Core/Debugger.cpp
+++ lldb/source/Core/Debugger.cpp
@@ -1198,6 +1198,29 @@
   return std::make_shared<StreamAsynchronousIO>(*this, false, GetUseColor());
 }
 
+void Debugger::RequestInterrupt() {
+  std::lock_guard<std::recursive_mutex> guard(m_interrupt_mutex);
+  m_interrupt_requested++;
+}
+
+void Debugger::CancelInterruptRequest() {
+  std::lock_guard<std::recursive_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::recursive_mutex> guard(m_interrupt_mutex);
+    return m_interrupt_requested != 0;
+  } else
+    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);
@@ -1943,7 +1966,15 @@
   data->Dump(stream.get());
 }
 
-bool Debugger::HasIOHandlerThread() { return m_io_handler_thread.IsJoinable(); }
+bool Debugger::HasIOHandlerThread() const {
+  return m_io_handler_thread.IsJoinable(); 
+}
+
+const 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()) {
@@ -1975,6 +2006,12 @@
   }
 }
 
+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())
Index: lldb/source/Commands/CommandObjectTarget.cpp
===================================================================
--- lldb/source/Commands/CommandObjectTarget.cpp
+++ lldb/source/Commands/CommandObjectTarget.cpp
@@ -2004,7 +2004,7 @@
             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 @@
                 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 @@
       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 @@
             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 @@
       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 @@
       }
 
       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 @@
       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 @@
             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 @@
         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(),
Index: lldb/source/API/SBDebugger.cpp
===================================================================
--- lldb/source/API/SBDebugger.cpp
+++ lldb/source/API/SBDebugger.cpp
@@ -1690,3 +1690,24 @@
   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;
+}
Index: lldb/source/API/SBCommandInterpreter.cpp
===================================================================
--- lldb/source/API/SBCommandInterpreter.cpp
+++ lldb/source/API/SBCommandInterpreter.cpp
@@ -141,7 +141,13 @@
 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) {
Index: lldb/include/lldb/Interpreter/CommandInterpreter.h
===================================================================
--- lldb/include/lldb/Interpreter/CommandInterpreter.h
+++ lldb/include/lldb/Interpreter/CommandInterpreter.h
@@ -354,7 +354,7 @@
   bool HandleCommand(const char *command_line, LazyBool add_to_history,
                      CommandReturnObject &result);
 
-  bool WasInterrupted() const;
+  bool InterruptCommand();
 
   /// Execute a list of commands in sequence.
   ///
@@ -639,6 +639,10 @@
 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;
@@ -701,7 +705,6 @@
 
   void StartHandlingCommand();
   void FinishHandlingCommand();
-  bool InterruptCommand();
 
   Debugger &m_debugger; // The debugger session that this interpreter is
                         // associated with
Index: lldb/include/lldb/Core/Debugger.h
===================================================================
--- lldb/include/lldb/Core/Debugger.h
+++ lldb/include/lldb/Core/Debugger.h
@@ -370,6 +370,53 @@
   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" flag on the debugger to support
+  /// cooperative interruption.  If this is non-zero, InterruptRequested will
+  /// return true.  Interruptible operations are expected to query this
+  /// flag periodically, and interrupt what they were doing if it is true.
+  ///
+  /// \param[in] message
+  ///   The new value of the InterruptRequested flag.
+  ///
+  void RequestInterrupt();
+  
+  /// Decrement the "interrupt requested" flag.  This is queried by
+  /// InterruptRequested, and any interruptible operations will be interrupted
+  /// while this flag is raised. 
+  ///
+  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 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
@@ -505,13 +552,19 @@
 
   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.
+  const HostThread SetIOHandlerThread(HostThread &new_thread);
 
   void JoinIOHandlerThread();
+  
+  bool IsIOHandlerThreadCurrentThread() const;
 
   lldb::thread_result_t IOHandlerThread();
 
@@ -590,6 +643,9 @@
   lldb::ListenerSP m_forward_listener_sp;
   llvm::once_flag m_clear_once;
   lldb::TargetSP m_dummy_target_sp;
+  uint32_t m_interrupt_requested = 0;
+  std::vector<std::string> m_interrupt_stack;
+  std::recursive_mutex m_interrupt_mutex;
 
   // Events for m_sync_broadcaster
   enum {
Index: lldb/include/lldb/API/SBDebugger.h
===================================================================
--- lldb/include/lldb/API/SBDebugger.h
+++ lldb/include/lldb/API/SBDebugger.h
@@ -197,6 +197,10 @@
   lldb::SBCommandInterpreter GetCommandInterpreter();
 
   void HandleCommand(const char *command);
+  
+  void RequestInterrupt();
+  void CancelInterruptRequest();
+  bool InterruptRequested();
 
   lldb::SBListener GetListener();
 
Index: lldb/include/lldb/API/SBCommandInterpreter.h
===================================================================
--- lldb/include/lldb/API/SBCommandInterpreter.h
+++ lldb/include/lldb/API/SBCommandInterpreter.h
@@ -241,7 +241,16 @@
                                        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.
+  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
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to