Skip to content

Conversation

@ashgti
Copy link
Contributor

@ashgti ashgti commented Jan 30, 2025

I just noticed with these changes lldb-dap was using 200% of my CPU and root causing the issue it seems that lldb_private::Pipe::Read() (without a timeout) is using a timeout of std::chrono::microseconds::zero() which ends up setting the SelectHelper timeout to now + 0 (see

select_helper.SetTimeout(timeout);
and
m_end_time = steady_clock::time_point(steady_clock::now() + timeout);
) which causes SelectHelper to return immediately with a timeout error. As a result the lldb_dap::OutputRedirector::RedirectTo() to turn into a busy loop.

Additionally, the 'read' call is waiting until the output buffer is full before returning, which prevents any partial output (see https://github.com/llvm/llvm-project/blob/7ceef1b1824073fcfd4724539f5942442da1a9c2/lldb/source/Host/posix/PipePosix.cpp#L325C9-L326C17).

This is not the desired behavior for lldb-dap. Instead we want a write to the FD to result in a callback to send the DAP Output event, which mean we want partial output.

To mitigate this, I'm reverting the reading operation to the previous behavior before 873426b but keeping the refactored structure and thread management.

I just noticed with these changes lldb-dap was using 200% of my CPU and root causing the issue it seems that lldb_private::Pipe::Read() (without a timeout) is using a timeout of `std::chrono::microseconds::zero()` which ends up setting the SelectHelper timeout to `now + 0`. This causes the `lldb_dap::OutputRedirector::RedirectTo()` to turn into a busy loop. Additionally, the 'Read' call is not peforming parital reads and will only invoke the redirect callback once the output buffer is filled.

This is not the desired behavior for lldb-dap. Instead we want a write to the FD to result in a callback to send the DAP Output event, which mean we want partial output.

To mitigate this, I'm reverting the reading operation to the previous behavior before 873426b but keeping the refactored structure and thread management.
@ashgti ashgti requested a review from JDevlieghere as a code owner January 30, 2025 23:22
@llvmbot llvmbot added the lldb label Jan 30, 2025
@ashgti ashgti requested a review from labath January 30, 2025 23:22
@llvmbot
Copy link
Member

llvmbot commented Jan 30, 2025

@llvm/pr-subscribers-lldb

Author: John Harrison (ashgti)

Changes

I just noticed with these changes lldb-dap was using 200% of my CPU and root causing the issue it seems that lldb_private::Pipe::Read() (without a timeout) is using a timeout of std::chrono::microseconds::zero() which ends up setting the SelectHelper timeout to now + 0 (see

select_helper.SetTimeout(timeout);
and
m_end_time = steady_clock::time_point(steady_clock::now() + timeout);
) which causes SelectHelper to return immediately with a timeout error. As a result the lldb_dap::OutputRedirector::RedirectTo() to turn into a busy loop.

Additionally, the 'read' call is waiting until the output buffer is full before returning, which prevents any partial output (see https://github.com/llvm/llvm-project/blob/7ceef1b1824073fcfd4724539f5942442da1a9c2/lldb/source/Host/posix/PipePosix.cpp#L325C9-L326C17).

This is not the desired behavior for lldb-dap. Instead we want a write to the FD to result in a callback to send the DAP Output event, which mean we want partial output.

To mitigate this, I'm reverting the reading operation to the previous behavior before 873426b but keeping the refactored structure and thread management.


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

2 Files Affected:

  • (modified) lldb/tools/lldb-dap/OutputRedirector.cpp (+42-24)
  • (modified) lldb/tools/lldb-dap/OutputRedirector.h (+4-2)
diff --git a/lldb/tools/lldb-dap/OutputRedirector.cpp b/lldb/tools/lldb-dap/OutputRedirector.cpp
index 8fcbcfec99c4431..7935e17a653bec3 100644
--- a/lldb/tools/lldb-dap/OutputRedirector.cpp
+++ b/lldb/tools/lldb-dap/OutputRedirector.cpp
@@ -6,6 +6,9 @@
 //
 //===----------------------------------------------------------------------===/
 
+#include "OutputRedirector.h"
+#include "DAP.h"
+#include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Error.h"
 #include <system_error>
 #if defined(_WIN32)
@@ -15,45 +18,59 @@
 #include <unistd.h>
 #endif
 
-#include "DAP.h"
-#include "OutputRedirector.h"
-#include "llvm/ADT/StringRef.h"
-
 using lldb_private::Pipe;
-using lldb_private::Status;
 using llvm::createStringError;
 using llvm::Error;
 using llvm::Expected;
+using llvm::inconvertibleErrorCode;
 using llvm::StringRef;
 
 namespace lldb_dap {
 
+int OutputRedirector::kInvalidDescriptor = -1;
+
+OutputRedirector::OutputRedirector() : m_fd(kInvalidDescriptor) {}
+
 Expected<int> OutputRedirector::GetWriteFileDescriptor() {
-  if (!m_pipe.CanWrite())
+  if (m_fd == kInvalidDescriptor)
     return createStringError(std::errc::bad_file_descriptor,
                              "write handle is not open for writing");
-  return m_pipe.GetWriteFileDescriptor();
+  return m_fd;
 }
 
 Error OutputRedirector::RedirectTo(std::function<void(StringRef)> callback) {
-  Status status = m_pipe.CreateNew(/*child_process_inherit=*/false);
-  if (status.Fail())
-    return status.takeError();
+  assert(m_fd == kInvalidDescriptor && "Output readirector already started.");
+  int new_fd[2];
 
-  m_forwarder = std::thread([this, callback]() {
-    char buffer[OutputBufferSize];
-    while (m_pipe.CanRead() && !m_stopped) {
-      size_t bytes_read;
-      Status status = m_pipe.Read(&buffer, sizeof(buffer), bytes_read);
-      if (status.Fail())
-        continue;
+#if defined(_WIN32)
+  if (::_pipe(new_fd, OutputBufferSize, O_TEXT) == -1) {
+#else
+  if (::pipe(new_fd) == -1) {
+#endif
+    int error = errno;
+    return createStringError(inconvertibleErrorCode(),
+                             "Couldn't create new pipe %s", strerror(error));
+  }
 
-      // EOF detected
-      if (bytes_read == 0 || m_stopped)
+  int read_fd = new_fd[0];
+  m_fd = new_fd[1];
+  m_forwarder = std::thread([this, callback, read_fd]() {
+    char buffer[OutputBufferSize];
+    while (!m_stopped) {
+      ssize_t bytes_count = ::read(read_fd, &buffer, sizeof(buffer));
+      // EOF detected.
+      if (bytes_count == 0)
+        break;
+      if (bytes_count == -1) {
+        // Skip non-fatal errors.
+        if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)
+          continue;
         break;
+      }
 
-      callback(StringRef(buffer, bytes_read));
+      callback(StringRef(buffer, bytes_count));
     }
+    ::close(read_fd);
   });
 
   return Error::success();
@@ -62,14 +79,15 @@ Error OutputRedirector::RedirectTo(std::function<void(StringRef)> callback) {
 void OutputRedirector::Stop() {
   m_stopped = true;
 
-  if (m_pipe.CanWrite()) {
+  if (m_fd != kInvalidDescriptor) {
+    int fd = m_fd;
+    m_fd = kInvalidDescriptor;
     // Closing the pipe may not be sufficient to wake up the thread in case the
     // write descriptor is duplicated (to stdout/err or to another process).
     // Write a null byte to ensure the read call returns.
     char buf[] = "\0";
-    size_t bytes_written;
-    m_pipe.Write(buf, sizeof(buf), bytes_written);
-    m_pipe.CloseWriteFileDescriptor();
+    ::write(fd, buf, sizeof(buf));
+    ::close(fd);
     m_forwarder.join();
   }
 }
diff --git a/lldb/tools/lldb-dap/OutputRedirector.h b/lldb/tools/lldb-dap/OutputRedirector.h
index 41ea05c22c69195..d2bd39797f3d75b 100644
--- a/lldb/tools/lldb-dap/OutputRedirector.h
+++ b/lldb/tools/lldb-dap/OutputRedirector.h
@@ -20,6 +20,8 @@ namespace lldb_dap {
 
 class OutputRedirector {
 public:
+  static int kInvalidDescriptor;
+
   /// Creates writable file descriptor that will invoke the given callback on
   /// each write in a background thread.
   ///
@@ -33,13 +35,13 @@ class OutputRedirector {
 
   ~OutputRedirector() { Stop(); }
 
-  OutputRedirector() = default;
+  OutputRedirector();
   OutputRedirector(const OutputRedirector &) = delete;
   OutputRedirector &operator=(const OutputRedirector &) = delete;
 
 private:
   std::atomic<bool> m_stopped = false;
-  lldb_private::Pipe m_pipe;
+  int m_fd;
   std::thread m_forwarder;
 };
 

@ashgti ashgti merged commit adb9ef0 into llvm:main Jan 31, 2025
7 of 8 checks passed
@vvereschaka
Copy link
Contributor

@ashgti ,

these changes break the windows build with the following errors:

C:\buildbot\as-builder-10\lldb-x-aarch64\llvm-project\lldb\tools\lldb-dap\IOStream.h(41): error C2061: syntax error: identifier 'SOCKET'
C:\buildbot\as-builder-10\lldb-x-aarch64\llvm-project\lldb\tools\lldb-dap\IOStream.h(48): error C3646: 'm_socket': unknown override specifier
C:\buildbot\as-builder-10\lldb-x-aarch64\llvm-project\lldb\tools\lldb-dap\IOStream.h(48): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
68.008 [200/18/5080]Building CXX object tools\lldb\tools\lldb-dap\CMakeFiles\lldb-dap.dir\FunctionBreakpoint.cpp.obj

https://lab.llvm.org/buildbot/#/builders/197/builds/1379/steps/8/logs/stdio

https://lab.llvm.org/buildbot/#/builders/197/builds/1379

would you take care of it?

@slydiman
Copy link
Contributor

Note that this error occurred on Windows host building tools/lldb/tools/lldb-dap/CMakeFiles/lldb-dap.dir/OutputRedirector.cpp using MSVC.

@ashgti
Copy link
Contributor Author

ashgti commented Jan 31, 2025

#125156 should fix this

JDevlieghere pushed a commit to swiftlang/llvm-project that referenced this pull request Feb 25, 2025
I just noticed with these changes lldb-dap was using 200% of my CPU and
root causing the issue it seems that lldb_private::Pipe::Read() (without
a timeout) is using a timeout of `std::chrono::microseconds::zero()`
which ends up setting the SelectHelper timeout to `now + 0` (see
https://github.com/llvm/llvm-project/blob/7ceef1b1824073fcfd4724539f5942442da1a9c2/lldb/source/Host/posix/PipePosix.cpp#L314
and
https://github.com/llvm/llvm-project/blob/7ceef1b1824073fcfd4724539f5942442da1a9c2/lldb/source/Utility/SelectHelper.cpp#L46)
which causes SelectHelper to return immediately with a timeout error. As
a result the `lldb_dap::OutputRedirector::RedirectTo()` to turn into a
busy loop.

Additionally, the 'read' call is waiting until the output buffer is full
before returning, which prevents any partial output (see
https://github.com/llvm/llvm-project/blob/7ceef1b1824073fcfd4724539f5942442da1a9c2/lldb/source/Host/posix/PipePosix.cpp#L325C9-L326C17).

This is not the desired behavior for lldb-dap. Instead we want a write
to the FD to result in a callback to send the DAP Output event, which
mean we want partial output.

To mitigate this, I'm reverting the reading operation to the previous
behavior before 873426b but keeping the
refactored structure and thread management.

(cherry picked from commit adb9ef0)
SquallATF pushed a commit to SquallATF/llvm-project that referenced this pull request Jun 13, 2025
I just noticed with these changes lldb-dap was using 200% of my CPU and
root causing the issue it seems that lldb_private::Pipe::Read() (without
a timeout) is using a timeout of `std::chrono::microseconds::zero()`
which ends up setting the SelectHelper timeout to `now + 0` (see
https://github.com/llvm/llvm-project/blob/7ceef1b1824073fcfd4724539f5942442da1a9c2/lldb/source/Host/posix/PipePosix.cpp#L314
and
https://github.com/llvm/llvm-project/blob/7ceef1b1824073fcfd4724539f5942442da1a9c2/lldb/source/Utility/SelectHelper.cpp#L46)
which causes SelectHelper to return immediately with a timeout error. As
a result the `lldb_dap::OutputRedirector::RedirectTo()` to turn into a
busy loop.

Additionally, the 'read' call is waiting until the output buffer is full
before returning, which prevents any partial output (see
https://github.com/llvm/llvm-project/blob/7ceef1b1824073fcfd4724539f5942442da1a9c2/lldb/source/Host/posix/PipePosix.cpp#L325C9-L326C17).

This is not the desired behavior for lldb-dap. Instead we want a write
to the FD to result in a callback to send the DAP Output event, which
mean we want partial output.

To mitigate this, I'm reverting the reading operation to the previous
behavior before 873426b but keeping the
refactored structure and thread management.
SquallATF pushed a commit to SquallATF/llvm-project that referenced this pull request Jul 14, 2025
I just noticed with these changes lldb-dap was using 200% of my CPU and
root causing the issue it seems that lldb_private::Pipe::Read() (without
a timeout) is using a timeout of `std::chrono::microseconds::zero()`
which ends up setting the SelectHelper timeout to `now + 0` (see
https://github.com/llvm/llvm-project/blob/7ceef1b1824073fcfd4724539f5942442da1a9c2/lldb/source/Host/posix/PipePosix.cpp#L314
and
https://github.com/llvm/llvm-project/blob/7ceef1b1824073fcfd4724539f5942442da1a9c2/lldb/source/Utility/SelectHelper.cpp#L46)
which causes SelectHelper to return immediately with a timeout error. As
a result the `lldb_dap::OutputRedirector::RedirectTo()` to turn into a
busy loop.

Additionally, the 'read' call is waiting until the output buffer is full
before returning, which prevents any partial output (see
https://github.com/llvm/llvm-project/blob/7ceef1b1824073fcfd4724539f5942442da1a9c2/lldb/source/Host/posix/PipePosix.cpp#L325C9-L326C17).

This is not the desired behavior for lldb-dap. Instead we want a write
to the FD to result in a callback to send the DAP Output event, which
mean we want partial output.

To mitigate this, I'm reverting the reading operation to the previous
behavior before 873426b but keeping the
refactored structure and thread management.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants