[Lldb-commits] [lldb] [lldb] Support non-blocking reads in JSONRPCTransport (PR #144610)

via lldb-commits lldb-commits at lists.llvm.org
Tue Jun 17 14:43:56 PDT 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

<details>
<summary>Changes</summary>

Support non-blocking reads for JSONRPCTransport so we can implement a multiplexed reader using the MainLoop. Pavel pointed out in #<!-- -->143628 that the implementation there (which was using blocking reads) can easily to reading partial JSON RPC packets.

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


3 Files Affected:

- (modified) lldb/include/lldb/Host/JSONTransport.h (+15-4) 
- (modified) lldb/source/Host/common/JSONTransport.cpp (+25-17) 
- (modified) lldb/unittests/Host/JSONTransportTest.cpp (+39-4) 


``````````diff
diff --git a/lldb/include/lldb/Host/JSONTransport.h b/lldb/include/lldb/Host/JSONTransport.h
index 4087cdf2b42f7..36a67c929a1c6 100644
--- a/lldb/include/lldb/Host/JSONTransport.h
+++ b/lldb/include/lldb/Host/JSONTransport.h
@@ -85,7 +85,8 @@ class JSONTransport {
 
   /// Reads the next message from the input stream.
   template <typename T>
-  llvm::Expected<T> Read(const std::chrono::microseconds &timeout) {
+  llvm::Expected<T>
+  Read(std::optional<std::chrono::microseconds> timeout = std::nullopt) {
     llvm::Expected<std::string> message = ReadImpl(timeout);
     if (!message)
       return message.takeError();
@@ -97,10 +98,20 @@ class JSONTransport {
 
   virtual llvm::Error WriteImpl(const std::string &message) = 0;
   virtual llvm::Expected<std::string>
-  ReadImpl(const std::chrono::microseconds &timeout) = 0;
+  ReadImpl(std::optional<std::chrono::microseconds> timeout) = 0;
+
+  llvm::Expected<std::string>
+  ReadFull(IOObject &descriptor, size_t length,
+           std::optional<std::chrono::microseconds> timeout) const;
+
+  llvm::Expected<std::string>
+  ReadUntil(IOObject &descriptor, llvm::StringRef delimiter,
+            std::optional<std::chrono::microseconds> timeout);
 
   lldb::IOObjectSP m_input;
   lldb::IOObjectSP m_output;
+
+  std::string m_buffer;
 };
 
 /// A transport class for JSON with a HTTP header.
@@ -113,7 +124,7 @@ class HTTPDelimitedJSONTransport : public JSONTransport {
 protected:
   virtual llvm::Error WriteImpl(const std::string &message) override;
   virtual llvm::Expected<std::string>
-  ReadImpl(const std::chrono::microseconds &timeout) override;
+  ReadImpl(std::optional<std::chrono::microseconds> timeout) override;
 
   // FIXME: Support any header.
   static constexpr llvm::StringLiteral kHeaderContentLength =
@@ -131,7 +142,7 @@ class JSONRPCTransport : public JSONTransport {
 protected:
   virtual llvm::Error WriteImpl(const std::string &message) override;
   virtual llvm::Expected<std::string>
-  ReadImpl(const std::chrono::microseconds &timeout) override;
+  ReadImpl(std::optional<std::chrono::microseconds> timeout) override;
 
   static constexpr llvm::StringLiteral kMessageSeparator = "\n";
 };
diff --git a/lldb/source/Host/common/JSONTransport.cpp b/lldb/source/Host/common/JSONTransport.cpp
index 1a0851d5c4365..0fae74fb87b68 100644
--- a/lldb/source/Host/common/JSONTransport.cpp
+++ b/lldb/source/Host/common/JSONTransport.cpp
@@ -27,9 +27,9 @@ using namespace lldb_private;
 
 /// ReadFull attempts to read the specified number of bytes. If EOF is
 /// encountered, an empty string is returned.
-static Expected<std::string>
-ReadFull(IOObject &descriptor, size_t length,
-         std::optional<std::chrono::microseconds> timeout = std::nullopt) {
+Expected<std::string> JSONTransport::ReadFull(
+    IOObject &descriptor, size_t length,
+    std::optional<std::chrono::microseconds> timeout) const {
   if (!descriptor.IsValid())
     return llvm::make_error<TransportInvalidError>();
 
@@ -67,19 +67,22 @@ ReadFull(IOObject &descriptor, size_t length,
   return data.substr(0, length);
 }
 
-static Expected<std::string>
-ReadUntil(IOObject &descriptor, StringRef delimiter,
-          std::optional<std::chrono::microseconds> timeout = std::nullopt) {
-  std::string buffer;
-  buffer.reserve(delimiter.size() + 1);
-  while (!llvm::StringRef(buffer).ends_with(delimiter)) {
+Expected<std::string>
+JSONTransport::ReadUntil(IOObject &descriptor, StringRef delimiter,
+                         std::optional<std::chrono::microseconds> timeout) {
+  if (!timeout || *timeout != std::chrono::microseconds::zero()) {
+    m_buffer.clear();
+    m_buffer.reserve(delimiter.size() + 1);
+  }
+
+  while (!llvm::StringRef(m_buffer).ends_with(delimiter)) {
     Expected<std::string> next =
-        ReadFull(descriptor, buffer.empty() ? delimiter.size() : 1, timeout);
+        ReadFull(descriptor, m_buffer.empty() ? delimiter.size() : 1, timeout);
     if (auto Err = next.takeError())
       return std::move(Err);
-    buffer += *next;
+    m_buffer += *next;
   }
-  return buffer.substr(0, buffer.size() - delimiter.size());
+  return m_buffer.substr(0, m_buffer.size() - delimiter.size());
 }
 
 JSONTransport::JSONTransport(IOObjectSP input, IOObjectSP output)
@@ -89,11 +92,15 @@ void JSONTransport::Log(llvm::StringRef message) {
   LLDB_LOG(GetLog(LLDBLog::Host), "{0}", message);
 }
 
-Expected<std::string>
-HTTPDelimitedJSONTransport::ReadImpl(const std::chrono::microseconds &timeout) {
+Expected<std::string> HTTPDelimitedJSONTransport::ReadImpl(
+    std::optional<std::chrono::microseconds> timeout) {
   if (!m_input || !m_input->IsValid())
     return llvm::make_error<TransportInvalidError>();
 
+  if (timeout && *timeout == std::chrono::microseconds::zero())
+    return llvm::createStringError(
+        "HTTPDelimitedJSONTransport does not support non-blocking reads");
+
   IOObject *input = m_input.get();
   Expected<std::string> message_header =
       ReadFull(*input, kHeaderContentLength.size(), timeout);
@@ -104,7 +111,8 @@ HTTPDelimitedJSONTransport::ReadImpl(const std::chrono::microseconds &timeout) {
                                      kHeaderContentLength, *message_header)
                                  .str());
 
-  Expected<std::string> raw_length = ReadUntil(*input, kHeaderSeparator);
+  Expected<std::string> raw_length =
+      ReadUntil(*input, kHeaderSeparator, timeout);
   if (!raw_length)
     return handleErrors(raw_length.takeError(),
                         [&](const TransportEOFError &E) -> llvm::Error {
@@ -117,7 +125,7 @@ HTTPDelimitedJSONTransport::ReadImpl(const std::chrono::microseconds &timeout) {
     return createStringError(
         formatv("invalid content length {0}", *raw_length).str());
 
-  Expected<std::string> raw_json = ReadFull(*input, length);
+  Expected<std::string> raw_json = ReadFull(*input, length, timeout);
   if (!raw_json)
     return handleErrors(
         raw_json.takeError(), [&](const TransportEOFError &E) -> llvm::Error {
@@ -143,7 +151,7 @@ Error HTTPDelimitedJSONTransport::WriteImpl(const std::string &message) {
 }
 
 Expected<std::string>
-JSONRPCTransport::ReadImpl(const std::chrono::microseconds &timeout) {
+JSONRPCTransport::ReadImpl(std::optional<std::chrono::microseconds> timeout) {
   if (!m_input || !m_input->IsValid())
     return make_error<TransportInvalidError>();
 
diff --git a/lldb/unittests/Host/JSONTransportTest.cpp b/lldb/unittests/Host/JSONTransportTest.cpp
index 4621869887ac8..cc43d7d851cb1 100644
--- a/lldb/unittests/Host/JSONTransportTest.cpp
+++ b/lldb/unittests/Host/JSONTransportTest.cpp
@@ -16,7 +16,7 @@ using namespace lldb_private;
 namespace {
 template <typename T> class JSONTransportTest : public PipeTest {
 protected:
-  std::unique_ptr<JSONTransport> transport;
+  std::unique_ptr<T> transport;
 
   void SetUp() override {
     PipeTest::SetUp();
@@ -36,7 +36,13 @@ class HTTPDelimitedJSONTransportTest
   using JSONTransportTest::JSONTransportTest;
 };
 
-class JSONRPCTransportTest : public JSONTransportTest<JSONRPCTransport> {
+class TestJSONRPCTransport : public JSONRPCTransport {
+public:
+  using JSONRPCTransport::JSONRPCTransport;
+  using JSONRPCTransport::WriteImpl; // For partial writes.
+};
+
+class JSONRPCTransportTest : public JSONTransportTest<TestJSONRPCTransport> {
 public:
   using JSONTransportTest::JSONTransportTest;
 };
@@ -84,7 +90,6 @@ TEST_F(HTTPDelimitedJSONTransportTest, ReadWithEOF) {
       Failed<TransportEOFError>());
 }
 
-
 TEST_F(HTTPDelimitedJSONTransportTest, InvalidTransport) {
   transport = std::make_unique<HTTPDelimitedJSONTransport>(nullptr, nullptr);
   ASSERT_THAT_EXPECTED(
@@ -142,13 +147,43 @@ TEST_F(JSONRPCTransportTest, Write) {
 }
 
 TEST_F(JSONRPCTransportTest, InvalidTransport) {
-  transport = std::make_unique<JSONRPCTransport>(nullptr, nullptr);
+  transport = std::make_unique<TestJSONRPCTransport>(nullptr, nullptr);
   ASSERT_THAT_EXPECTED(
       transport->Read<JSONTestType>(std::chrono::milliseconds(1)),
       Failed<TransportInvalidError>());
 }
 
 #ifndef _WIN32
+TEST_F(HTTPDelimitedJSONTransportTest, NonBlockingRead) {
+  ASSERT_THAT_EXPECTED(
+      transport->Read<JSONTestType>(std::chrono::microseconds::zero()),
+      llvm::FailedWithMessage(
+          "HTTPDelimitedJSONTransport does not support non-blocking reads"));
+}
+
+TEST_F(JSONRPCTransportTest, NonBlockingRead) {
+  llvm::StringRef head = R"({"str")";
+  llvm::StringRef tail = R"(: "foo"})"
+                         "\n";
+
+  ASSERT_THAT_EXPECTED(input.Write(head.data(), head.size()), Succeeded());
+  ASSERT_THAT_EXPECTED(
+      transport->Read<JSONTestType>(std::chrono::microseconds::zero()),
+      Failed<TransportTimeoutError>());
+
+  ASSERT_THAT_EXPECTED(input.Write(tail.data(), tail.size()), Succeeded());
+  while (true) {
+    llvm::Expected<JSONTestType> result =
+        transport->Read<JSONTestType>(std::chrono::microseconds::zero());
+    if (result.errorIsA<TransportTimeoutError>()) {
+      llvm::consumeError(result.takeError());
+      continue;
+    }
+    ASSERT_THAT_EXPECTED(result, HasValue(testing::FieldsAre(/*str=*/"foo")));
+    break;
+  }
+}
+
 TEST_F(HTTPDelimitedJSONTransportTest, ReadWithTimeout) {
   ASSERT_THAT_EXPECTED(
       transport->Read<JSONTestType>(std::chrono::milliseconds(1)),

``````````

</details>


https://github.com/llvm/llvm-project/pull/144610


More information about the lldb-commits mailing list