diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -4310,6 +4310,12 @@ ~IOHandlerProcessSTDIO() override = default; + void SetIsRunning(bool running) { + std::lock_guard guard(m_mutex); + SetIsDone(!running); + m_is_running = running; + } + // Each IOHandler gets to run until it is done. It should read data from the // "in" and place output into "out" and "err and return when done. void Run() override { @@ -4329,49 +4335,52 @@ // FD_ZERO, FD_SET are not supported on windows #ifndef _WIN32 const int pipe_read_fd = m_pipe.GetReadFileDescriptor(); - m_is_running = true; - while (!GetIsDone()) { + SetIsRunning(true); + while (true) { + { + std::lock_guard guard(m_mutex); + if (GetIsDone()) + break; + } + SelectHelper select_helper; select_helper.FDSetRead(read_fd); select_helper.FDSetRead(pipe_read_fd); Status error = select_helper.Select(); - if (error.Fail()) { - SetIsDone(true); - } else { - char ch = 0; - size_t n; - if (select_helper.FDIsSetRead(read_fd)) { - n = 1; - if (m_read_file.Read(&ch, n).Success() && n == 1) { - if (m_write_file.Write(&ch, n).Fail() || n != 1) - SetIsDone(true); - } else - SetIsDone(true); - } + if (error.Fail()) + break; + + char ch = 0; + size_t n; + if (select_helper.FDIsSetRead(read_fd)) { + n = 1; + if (m_read_file.Read(&ch, n).Success() && n == 1) { + if (m_write_file.Write(&ch, n).Fail() || n != 1) + break; + } else + break; + if (select_helper.FDIsSetRead(pipe_read_fd)) { size_t bytes_read; // Consume the interrupt byte Status error = m_pipe.Read(&ch, 1, bytes_read); if (error.Success()) { - switch (ch) { - case 'q': - SetIsDone(true); + if (ch == 'q') break; - case 'i': + if (ch == 'i') if (StateIsRunningState(m_process->GetState())) m_process->SendAsyncInterrupt(); - break; - } } } } } - m_is_running = false; + SetIsRunning(false); #endif } void Cancel() override { + std::lock_guard guard(m_mutex); SetIsDone(true); // Only write to our pipe to cancel if we are in // IOHandlerProcessSTDIO::Run(). We can end up with a python command that @@ -4428,7 +4437,8 @@ NativeFile m_write_file; // Write to this file (usually the primary pty for // getting io to debuggee) Pipe m_pipe; - std::atomic m_is_running{false}; + std::mutex m_mutex; + bool m_is_running = false; }; void Process::SetSTDIOFileDescriptor(int fd) { diff --git a/lldb/test/API/iohandler/stdio/Makefile b/lldb/test/API/iohandler/stdio/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/iohandler/stdio/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/iohandler/stdio/TestIOHandlerProcessSTDIO.py b/lldb/test/API/iohandler/stdio/TestIOHandlerProcessSTDIO.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/iohandler/stdio/TestIOHandlerProcessSTDIO.py @@ -0,0 +1,30 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test.lldbpexpect import PExpectTest + +class TestIOHandlerProcessSTDIO(PExpectTest): + + mydir = TestBase.compute_mydir(__file__) + NO_DEBUG_INFO_TESTCASE = True + + # PExpect uses many timeouts internally and doesn't play well + # under ASAN on a loaded machine.. + @skipIfAsan + def test(self): + self.build() + self.launch(executable=self.getBuildArtifact("a.out")) + self.child.sendline("run") + + self.child.send("foo\n") + self.child.expect_exact("stdout: foo") + + self.child.send("bar\n") + self.child.expect_exact("stdout: bar") + + self.child.send("baz\n") + self.child.expect_exact("stdout: baz") + + self.child.sendcontrol('d') + self.expect_prompt() + self.quit() diff --git a/lldb/test/API/iohandler/stdio/main.cpp b/lldb/test/API/iohandler/stdio/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/iohandler/stdio/main.cpp @@ -0,0 +1,8 @@ +#include +#include + +int main(int argc, char **argv) { + for (std::string line; std::getline(std::cin, line);) + std::cout << "stdout: " << line << '\n'; + return 0; +}