[Lldb-commits] [lldb] [lldb] Migrating MainLoopWindows to files and sockets. (PR #145621)
John Harrison via lldb-commits
lldb-commits at lists.llvm.org
Tue Jun 24 17:55:48 PDT 2025
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 <harjohn at 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) {
More information about the lldb-commits
mailing list