https://github.com/ashgti created 
https://github.com/llvm/llvm-project/pull/145621

This updates MainLoopWindows to support events for reading from a file and a 
socket type.

This unifies both handle types using `WaitForMultipleEvents` which can listen 
to both sockets and files for change events.

This should allow us to unify how we handle watching files/pipes/sockets on 
Windows and Posix systems.

>From 21652f8b18f122027bd62759a799faccbc8c21b8 Mon Sep 17 00:00:00 2001
From: John Harrison <harj...@google.com>
Date: Tue, 24 Jun 2025 17:48:54 -0700
Subject: [PATCH] [lldb] Migrating MainLoopWindows to files and sockets.

This updates MainLoopWindows to support events for reading from a file and a 
socket type.

This unifies both handle types using `WaitForMultipleEvents` which can listen 
to both sockets and files for change events.

This should allow us to unify how we handle watching files/pipes/sockets on 
Windows and Posix systems.
---
 lldb/include/lldb/Host/JSONTransport.h        |  13 +--
 .../lldb/Host/windows/MainLoopWindows.h       |   3 +-
 lldb/include/lldb/Utility/IOObject.h          |   7 +-
 lldb/source/Host/common/File.cpp              |   4 +
 lldb/source/Host/common/JSONTransport.cpp     |  36 ++----
 lldb/source/Host/common/Socket.cpp            |  13 ++-
 .../posix/ConnectionFileDescriptorPosix.cpp   |  13 ++-
 lldb/source/Host/windows/MainLoopWindows.cpp  | 108 ++++++++++--------
 lldb/source/Utility/IOObject.cpp              |   8 ++
 lldb/tools/lldb-dap/DAP.cpp                   |   2 +-
 lldb/unittests/Host/FileTest.cpp              |   2 +-
 11 files changed, 112 insertions(+), 97 deletions(-)

diff --git a/lldb/include/lldb/Host/JSONTransport.h 
b/lldb/include/lldb/Host/JSONTransport.h
index 4087cdf2b42f7..348431ed662ed 100644
--- a/lldb/include/lldb/Host/JSONTransport.h
+++ b/lldb/include/lldb/Host/JSONTransport.h
@@ -85,8 +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<std::string> message = ReadImpl(timeout);
+  llvm::Expected<T> Read() {
+    llvm::Expected<std::string> message = ReadImpl();
     if (!message)
       return message.takeError();
     return llvm::json::parse<T>(/*JSON=*/*message);
@@ -96,8 +96,7 @@ class JSONTransport {
   virtual void Log(llvm::StringRef message);
 
   virtual llvm::Error WriteImpl(const std::string &message) = 0;
-  virtual llvm::Expected<std::string>
-  ReadImpl(const std::chrono::microseconds &timeout) = 0;
+  virtual llvm::Expected<std::string> ReadImpl() = 0;
 
   lldb::IOObjectSP m_input;
   lldb::IOObjectSP m_output;
@@ -112,8 +111,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;
+  virtual llvm::Expected<std::string> ReadImpl() override;
 
   // FIXME: Support any header.
   static constexpr llvm::StringLiteral kHeaderContentLength =
@@ -130,8 +128,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;
+  virtual llvm::Expected<std::string> ReadImpl() override;
 
   static constexpr llvm::StringLiteral kMessageSeparator = "\n";
 };
diff --git a/lldb/include/lldb/Host/windows/MainLoopWindows.h 
b/lldb/include/lldb/Host/windows/MainLoopWindows.h
index 3937a24645d95..d47bf7d5f50b7 100644
--- a/lldb/include/lldb/Host/windows/MainLoopWindows.h
+++ b/lldb/include/lldb/Host/windows/MainLoopWindows.h
@@ -37,11 +37,10 @@ class MainLoopWindows : public MainLoopBase {
   void Interrupt() override;
 
 private:
-  void ProcessReadObject(IOObject::WaitableHandle handle);
   llvm::Expected<size_t> Poll();
 
   struct FdInfo {
-    void *event;
+    const lldb::IOObjectSP &object_sp;
     Callback callback;
   };
   llvm::DenseMap<IOObject::WaitableHandle, FdInfo> m_read_fds;
diff --git a/lldb/include/lldb/Utility/IOObject.h 
b/lldb/include/lldb/Utility/IOObject.h
index 8cf42992e7be5..de6532a637083 100644
--- a/lldb/include/lldb/Utility/IOObject.h
+++ b/lldb/include/lldb/Utility/IOObject.h
@@ -14,6 +14,7 @@
 #include <sys/types.h>
 
 #include "lldb/lldb-private.h"
+#include "lldb/lldb-types.h"
 
 namespace lldb_private {
 
@@ -24,9 +25,9 @@ class IOObject {
     eFDTypeSocket, // Socket requiring send/recv
   };
 
-  // TODO: On Windows this should be a HANDLE, and wait should use
-  // WaitForMultipleObjects
-  typedef int WaitableHandle;
+  // A handle for integrating with the host event loop model.
+  using WaitableHandle = lldb::file_t;
+
   static const WaitableHandle kInvalidHandleValue;
 
   IOObject(FDType type) : m_fd_type(type) {}
diff --git a/lldb/source/Host/common/File.cpp b/lldb/source/Host/common/File.cpp
index 9aa95ffda44cb..23b6dc9fe850d 100644
--- a/lldb/source/Host/common/File.cpp
+++ b/lldb/source/Host/common/File.cpp
@@ -274,7 +274,11 @@ int NativeFile::GetDescriptor() const {
 }
 
 IOObject::WaitableHandle NativeFile::GetWaitableHandle() {
+#ifdef _WIN32
+  return (HANDLE)_get_osfhandle(GetDescriptor());
+#else
   return GetDescriptor();
+#endif
 }
 
 FILE *NativeFile::GetStream() {
diff --git a/lldb/source/Host/common/JSONTransport.cpp 
b/lldb/source/Host/common/JSONTransport.cpp
index 1a0851d5c4365..b5e271f61e17a 100644
--- a/lldb/source/Host/common/JSONTransport.cpp
+++ b/lldb/source/Host/common/JSONTransport.cpp
@@ -28,31 +28,10 @@ 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) {
+ReadFull(IOObject &descriptor, size_t length) {
   if (!descriptor.IsValid())
     return llvm::make_error<TransportInvalidError>();
 
-  bool timeout_supported = true;
-  // FIXME: SelectHelper does not work with NativeFile on Win32.
-#if _WIN32
-  timeout_supported = descriptor.GetFdType() == IOObject::eFDTypeSocket;
-#endif
-
-  if (timeout && timeout_supported) {
-    SelectHelper sh;
-    sh.SetTimeout(*timeout);
-    sh.FDSetRead(descriptor.GetWaitableHandle());
-    Status status = sh.Select();
-    if (status.Fail()) {
-      // Convert timeouts into a specific error.
-      if (status.GetType() == lldb::eErrorTypePOSIX &&
-          status.GetError() == ETIMEDOUT)
-        return make_error<TransportTimeoutError>();
-      return status.takeError();
-    }
-  }
-
   std::string data;
   data.resize(length);
   Status status = descriptor.Read(data.data(), length);
@@ -68,13 +47,12 @@ ReadFull(IOObject &descriptor, size_t length,
 }
 
 static Expected<std::string>
-ReadUntil(IOObject &descriptor, StringRef delimiter,
-          std::optional<std::chrono::microseconds> timeout = std::nullopt) {
+ReadUntil(IOObject &descriptor, StringRef delimiter) {
   std::string buffer;
   buffer.reserve(delimiter.size() + 1);
   while (!llvm::StringRef(buffer).ends_with(delimiter)) {
     Expected<std::string> next =
-        ReadFull(descriptor, buffer.empty() ? delimiter.size() : 1, timeout);
+        ReadFull(descriptor, buffer.empty() ? delimiter.size() : 1);
     if (auto Err = next.takeError())
       return std::move(Err);
     buffer += *next;
@@ -90,13 +68,13 @@ void JSONTransport::Log(llvm::StringRef message) {
 }
 
 Expected<std::string>
-HTTPDelimitedJSONTransport::ReadImpl(const std::chrono::microseconds &timeout) 
{
+HTTPDelimitedJSONTransport::ReadImpl() {
   if (!m_input || !m_input->IsValid())
     return llvm::make_error<TransportInvalidError>();
 
   IOObject *input = m_input.get();
   Expected<std::string> message_header =
-      ReadFull(*input, kHeaderContentLength.size(), timeout);
+      ReadFull(*input, kHeaderContentLength.size());
   if (!message_header)
     return message_header.takeError();
   if (*message_header != kHeaderContentLength)
@@ -143,13 +121,13 @@ Error HTTPDelimitedJSONTransport::WriteImpl(const 
std::string &message) {
 }
 
 Expected<std::string>
-JSONRPCTransport::ReadImpl(const std::chrono::microseconds &timeout) {
+JSONRPCTransport::ReadImpl() {
   if (!m_input || !m_input->IsValid())
     return make_error<TransportInvalidError>();
 
   IOObject *input = m_input.get();
   Expected<std::string> raw_json =
-      ReadUntil(*input, kMessageSeparator, timeout);
+      ReadUntil(*input, kMessageSeparator);
   if (!raw_json)
     return raw_json.takeError();
 
diff --git a/lldb/source/Host/common/Socket.cpp 
b/lldb/source/Host/common/Socket.cpp
index 2b23fd1e6e57e..10c28a5456f89 100644
--- a/lldb/source/Host/common/Socket.cpp
+++ b/lldb/source/Host/common/Socket.cpp
@@ -313,8 +313,19 @@ Socket::DecodeHostAndPort(llvm::StringRef host_and_port) {
 }
 
 IOObject::WaitableHandle Socket::GetWaitableHandle() {
-  // TODO: On Windows, use WSAEventSelect
+#ifdef _WIN32
+  if (m_socket == INVALID_SOCKET)
+    return IOObject::kInvalidHandleValue;
+  
+  WSAEVENT event = WSACreateEvent();
+  assert(event != WSA_INVALID_EVENT);
+  if (WSAEventSelect(m_socket, event, FD_ACCEPT | FD_READ | FD_WRITE) != 0)
+    return IOObject::kInvalidHandleValue;
+  
+  return event;
+#else
   return m_socket;
+#endif
 }
 
 Status Socket::Read(void *buf, size_t &num_bytes) {
diff --git a/lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp 
b/lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp
index 57dce44812c89..86a23b4f647cf 100644
--- a/lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp
+++ b/lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp
@@ -276,7 +276,7 @@ size_t ConnectionFileDescriptor::Read(void *dst, size_t 
dst_len,
               "%p ConnectionFileDescriptor::Read()  fd = %" PRIu64
               ", dst = %p, dst_len = %" PRIu64 ") => %" PRIu64 ", error = %s",
               static_cast<void *>(this),
-              static_cast<uint64_t>(m_io_sp->GetWaitableHandle()),
+              static_cast<file_t>(m_io_sp->GetWaitableHandle()),
               static_cast<void *>(dst), static_cast<uint64_t>(dst_len),
               static_cast<uint64_t>(bytes_read), error.AsCString());
   }
@@ -380,7 +380,7 @@ size_t ConnectionFileDescriptor::Write(const void *src, 
size_t src_len,
               "%p ConnectionFileDescriptor::Write(fd = %" PRIu64
               ", src = %p, src_len = %" PRIu64 ") => %" PRIu64 " (error = %s)",
               static_cast<void *>(this),
-              static_cast<uint64_t>(m_io_sp->GetWaitableHandle()),
+              static_cast<file_t>(m_io_sp->GetWaitableHandle()),
               static_cast<const void *>(src), static_cast<uint64_t>(src_len),
               static_cast<uint64_t>(bytes_sent), error.AsCString());
   }
@@ -451,14 +451,17 @@ ConnectionFileDescriptor::BytesAvailable(const 
Timeout<std::micro> &timeout,
     if (timeout)
       select_helper.SetTimeout(*timeout);
 
-    select_helper.FDSetRead(handle);
+    // FIXME: Migrate to MainLoop.
 #if defined(_WIN32)
+    if (const auto *sock = static_cast<Socket*>(m_io_sp.get()))
+      select_helper.FDSetRead((socket_t)sock->GetNativeSocket());
     // select() won't accept pipes on Windows.  The entire Windows codepath
     // needs to be converted over to using WaitForMultipleObjects and event
     // HANDLEs, but for now at least this will allow ::select() to not return
     // an error.
     const bool have_pipe_fd = false;
 #else
+    select_helper.FDSetRead(handle);
     const bool have_pipe_fd = pipe_fd >= 0;
 #endif
     if (have_pipe_fd)
@@ -493,7 +496,11 @@ ConnectionFileDescriptor::BytesAvailable(const 
Timeout<std::micro> &timeout,
           break; // Lets keep reading to until we timeout
         }
       } else {
+#if defined(_WIN32)
+        if (const auto *sock = static_cast<Socket*>(m_io_sp.get()); 
select_helper.FDIsSetRead(sock->GetNativeSocket()))
+#else
         if (select_helper.FDIsSetRead(handle))
+#endif
           return eConnectionStatusSuccess;
 
         if (select_helper.FDIsSetRead(pipe_fd)) {
diff --git a/lldb/source/Host/windows/MainLoopWindows.cpp 
b/lldb/source/Host/windows/MainLoopWindows.cpp
index f3ab2a710cd01..78e392eea255f 100644
--- a/lldb/source/Host/windows/MainLoopWindows.cpp
+++ b/lldb/source/Host/windows/MainLoopWindows.cpp
@@ -8,8 +8,11 @@
 
 #include "lldb/Host/windows/MainLoopWindows.h"
 #include "lldb/Host/Config.h"
+#include "lldb/Host/Socket.h"
+#include "llvm/Support/Casting.h"
 #include "lldb/Utility/Status.h"
 #include "llvm/Config/llvm-config.h"
+#include "llvm/Support/WindowsError.h"
 #include <algorithm>
 #include <cassert>
 #include <cerrno>
@@ -25,12 +28,41 @@ static DWORD 
ToTimeout(std::optional<MainLoopWindows::TimePoint> point) {
   using namespace std::chrono;
 
   if (!point)
-    return WSA_INFINITE;
+    return INFINITE;
 
   nanoseconds dur = (std::max)(*point - steady_clock::now(), nanoseconds(0));
   return ceil<milliseconds>(dur).count();
 }
 
+static void CloseSocketWaitHandle(IOObject::WaitableHandle handle) {
+  BOOL result = WSACloseEvent(handle);
+  assert(result == TRUE);
+  UNUSED_IF_ASSERT_DISABLED(result);
+}
+
+static bool BytesAvailable(IOObject::WaitableHandle handle, const IOObjectSP 
&object) {
+  DWORD available_bytes = 0;
+  switch (object->GetFdType()) {
+  case IOObject::eFDTypeFile:
+    return !PeekNamedPipe(handle, NULL, 0, NULL, &available_bytes, NULL) ||
+           available_bytes > 0;
+  case IOObject::eFDTypeSocket:
+    auto sock = static_cast<const Socket *>(object.get());
+    if (sock == nullptr)
+      return false;
+    WSANETWORKEVENTS events;
+    if (WSAEnumNetworkEvents(sock->GetNativeSocket(), (WSAEVENT)handle, 
&events) != 0)
+      return false;
+    if (events.lNetworkEvents & FD_CLOSE || events.lNetworkEvents & FD_ACCEPT) 
{
+                       available_bytes = 1;
+               } else if (events.lNetworkEvents & FD_READ) {
+                       ioctlsocket(sock->GetNativeSocket(), FIONREAD, 
&available_bytes);
+               }
+    return available_bytes > 0;
+  }
+  llvm_unreachable();
+}
+
 MainLoopWindows::MainLoopWindows() {
   m_interrupt_event = WSACreateEvent();
   assert(m_interrupt_event != WSA_INVALID_EVENT);
@@ -38,42 +70,33 @@ MainLoopWindows::MainLoopWindows() {
 
 MainLoopWindows::~MainLoopWindows() {
   assert(m_read_fds.empty());
-  BOOL result = WSACloseEvent(m_interrupt_event);
-  assert(result == TRUE);
-  UNUSED_IF_ASSERT_DISABLED(result);
+  CloseSocketWaitHandle(m_interrupt_event);
 }
 
 llvm::Expected<size_t> MainLoopWindows::Poll() {
-  std::vector<WSAEVENT> events;
+  std::vector<HANDLE> events;
   events.reserve(m_read_fds.size() + 1);
-  for (auto &[fd, info] : m_read_fds) {
-    int result = WSAEventSelect(fd, info.event, FD_READ | FD_ACCEPT | 
FD_CLOSE);
-    assert(result == 0);
-    UNUSED_IF_ASSERT_DISABLED(result);
-
-    events.push_back(info.event);
+  for (auto &[fd, _] : m_read_fds) {
+    events.push_back(fd);
   }
   events.push_back(m_interrupt_event);
 
   DWORD result =
-      WSAWaitForMultipleEvents(events.size(), events.data(), FALSE,
-                               ToTimeout(GetNextWakeupTime()), FALSE);
-
-  for (auto &fd : m_read_fds) {
-    int result = WSAEventSelect(fd.first, WSA_INVALID_EVENT, 0);
-    assert(result == 0);
-    UNUSED_IF_ASSERT_DISABLED(result);
-  }
-
-  if (result >= WSA_WAIT_EVENT_0 && result < WSA_WAIT_EVENT_0 + events.size())
-    return result - WSA_WAIT_EVENT_0;
+      WaitForMultipleObjects(events.size(), events.data(), FALSE,
+                               ToTimeout(GetNextWakeupTime()));
 
   // A timeout is treated as a (premature) signalization of the interrupt 
event.
-  if (result == WSA_WAIT_TIMEOUT)
+  if (result == WAIT_TIMEOUT)
     return events.size() - 1;
 
+  if (result == WAIT_FAILED)
+    return llvm::createStringError(llvm::mapLastWindowsError(), 
"WaitForMultipleObjects failed");
+
+  if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + events.size())
+    return result - WAIT_OBJECT_0;
+
   return llvm::createStringError(llvm::inconvertibleErrorCode(),
-                                 "WSAWaitForMultipleEvents failed");
+                                 "WaitForMultipleEvents failed");
 }
 
 MainLoopWindows::ReadHandleUP
@@ -83,28 +106,19 @@ MainLoopWindows::RegisterReadObject(const IOObjectSP 
&object_sp,
     error = Status::FromErrorString("IO object is not valid.");
     return nullptr;
   }
-  if (object_sp->GetFdType() != IOObject::eFDTypeSocket) {
-    error = Status::FromErrorString(
-        "MainLoopWindows: non-socket types unsupported on Windows");
-    return nullptr;
-  }
 
-  WSAEVENT event = WSACreateEvent();
-  if (event == WSA_INVALID_EVENT) {
-    error =
-        Status::FromErrorStringWithFormat("Cannot create monitoring event.");
-    return nullptr;
-  }
+  IOObject::WaitableHandle waitable_handle = object_sp->GetWaitableHandle();
 
   const bool inserted =
       m_read_fds
-          .try_emplace(object_sp->GetWaitableHandle(), FdInfo{event, callback})
+          .try_emplace(waitable_handle, FdInfo{object_sp, callback})
           .second;
   if (!inserted) {
-    WSACloseEvent(event);
+    if (object_sp->GetFdType() == IOObject::eFDTypeSocket)
+      CloseSocketWaitHandle(waitable_handle);
     error = Status::FromErrorStringWithFormat(
         "File descriptor %d already monitored.",
-        object_sp->GetWaitableHandle());
+        waitable_handle);
     return nullptr;
   }
 
@@ -114,18 +128,11 @@ MainLoopWindows::RegisterReadObject(const IOObjectSP 
&object_sp,
 void MainLoopWindows::UnregisterReadObject(IOObject::WaitableHandle handle) {
   auto it = m_read_fds.find(handle);
   assert(it != m_read_fds.end());
-  BOOL result = WSACloseEvent(it->second.event);
-  assert(result == TRUE);
-  UNUSED_IF_ASSERT_DISABLED(result);
+  if (it->second.object_sp->GetFdType() == IOObject::eFDTypeSocket)
+    CloseSocketWaitHandle(handle);
   m_read_fds.erase(it);
 }
 
-void MainLoopWindows::ProcessReadObject(IOObject::WaitableHandle handle) {
-  auto it = m_read_fds.find(handle);
-  if (it != m_read_fds.end())
-    it->second.callback(*this); // Do the work
-}
-
 Status MainLoopWindows::Run() {
   m_terminate_request = false;
 
@@ -138,8 +145,11 @@ Status MainLoopWindows::Run() {
 
     if (*signaled_event < m_read_fds.size()) {
       auto &KV = *std::next(m_read_fds.begin(), *signaled_event);
-      WSAResetEvent(KV.second.event);
-      ProcessReadObject(KV.first);
+      if (BytesAvailable(KV.first, KV.second.object_sp)) {
+        if (KV.second.object_sp->GetFdType() == IOObject::eFDTypeSocket)
+          WSAResetEvent(KV.first);
+        KV.second.callback(*this); // Do the work.
+      }
     } else {
       assert(*signaled_event == m_read_fds.size());
       WSAResetEvent(m_interrupt_event);
diff --git a/lldb/source/Utility/IOObject.cpp b/lldb/source/Utility/IOObject.cpp
index 964edce0ce10d..69065182715c2 100644
--- a/lldb/source/Utility/IOObject.cpp
+++ b/lldb/source/Utility/IOObject.cpp
@@ -8,7 +8,15 @@
 
 #include "lldb/Utility/IOObject.h"
 
+#ifdef _WIN32
+#include "lldb/Host/windows/windows.h"
+#endif
+
 using namespace lldb_private;
 
+#ifdef _WIN32
+const IOObject::WaitableHandle IOObject::kInvalidHandleValue = 
INVALID_HANDLE_VALUE;
+#else
 const IOObject::WaitableHandle IOObject::kInvalidHandleValue = -1;
+#endif
 IOObject::~IOObject() = default;
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index c171b55951cb5..7d388436bb1d6 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -881,7 +881,7 @@ llvm::Error DAP::Loop() {
 
         while (!disconnecting) {
           llvm::Expected<Message> next =
-              transport.Read<protocol::Message>(std::chrono::seconds(1));
+              transport.Read<protocol::Message>();
           if (next.errorIsA<TransportEOFError>()) {
             consumeError(next.takeError());
             break;
diff --git a/lldb/unittests/Host/FileTest.cpp b/lldb/unittests/Host/FileTest.cpp
index 35c87bb200fad..529e6a5e12abe 100644
--- a/lldb/unittests/Host/FileTest.cpp
+++ b/lldb/unittests/Host/FileTest.cpp
@@ -32,7 +32,7 @@ TEST(File, GetWaitableHandleFileno) {
   ASSERT_TRUE(stream);
 
   NativeFile file(stream, true);
-  EXPECT_EQ(file.GetWaitableHandle(), fd);
+  EXPECT_EQ(file.GetWaitableHandle(), (file_t)fd);
 }
 
 TEST(File, GetStreamFromDescriptor) {

_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to