Skip to content

Commit 1a7b7e2

Browse files
ashgtilabath
andauthored
[lldb] Adding pipe support to lldb_private::MainLoopWindows. (#145621)
This updates MainLoopWindows to support events for reading from a pipe (both anonymous and named pipes) as well as sockets. This unifies both handle types using `WSAWaitForMultipleEvents` which can listen to both sockets and handles for change events. This should allow us to unify how we handle watching pipes/sockets on Windows and Posix systems. We can extend this in the future if we want to support watching other types, like files or even other events like a process life time. --------- Co-authored-by: Pavel Labath <[email protected]>
1 parent 47e28d9 commit 1a7b7e2

File tree

10 files changed

+262
-60
lines changed

10 files changed

+262
-60
lines changed

lldb/include/lldb/Host/windows/MainLoopWindows.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,30 @@ class MainLoopWindows : public MainLoopBase {
3131

3232
Status Run() override;
3333

34+
class IOEvent {
35+
public:
36+
IOEvent(IOObject::WaitableHandle event) : m_event(event) {}
37+
virtual ~IOEvent() {}
38+
virtual void WillPoll() {}
39+
virtual void DidPoll() {}
40+
virtual void Disarm() {}
41+
IOObject::WaitableHandle GetHandle() { return m_event; }
42+
43+
protected:
44+
IOObject::WaitableHandle m_event;
45+
};
46+
using IOEventUP = std::unique_ptr<IOEvent>;
47+
3448
protected:
3549
void UnregisterReadObject(IOObject::WaitableHandle handle) override;
3650

3751
void Interrupt() override;
3852

3953
private:
40-
void ProcessReadObject(IOObject::WaitableHandle handle);
4154
llvm::Expected<size_t> Poll();
4255

4356
struct FdInfo {
44-
void *event;
57+
IOEventUP event;
4558
Callback callback;
4659
};
4760
llvm::DenseMap<IOObject::WaitableHandle, FdInfo> m_read_fds;

lldb/include/lldb/Utility/IOObject.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <sys/types.h>
1515

1616
#include "lldb/lldb-private.h"
17+
#include "lldb/lldb-types.h"
1718

1819
namespace lldb_private {
1920

@@ -24,9 +25,9 @@ class IOObject {
2425
eFDTypeSocket, // Socket requiring send/recv
2526
};
2627

27-
// TODO: On Windows this should be a HANDLE, and wait should use
28-
// WaitForMultipleObjects
29-
typedef int WaitableHandle;
28+
// A handle for integrating with the host event loop model.
29+
using WaitableHandle = lldb::file_t;
30+
3031
static const WaitableHandle kInvalidHandleValue;
3132

3233
IOObject(FDType type) : m_fd_type(type) {}

lldb/source/Host/common/File.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,11 @@ int NativeFile::GetDescriptor() const {
274274
}
275275

276276
IOObject::WaitableHandle NativeFile::GetWaitableHandle() {
277+
#ifdef _WIN32
278+
return (HANDLE)_get_osfhandle(GetDescriptor());
279+
#else
277280
return GetDescriptor();
281+
#endif
278282
}
279283

280284
FILE *NativeFile::GetStream() {

lldb/source/Host/common/JSONTransport.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ ReadFull(IOObject &descriptor, size_t length,
4242
if (timeout && timeout_supported) {
4343
SelectHelper sh;
4444
sh.SetTimeout(*timeout);
45-
sh.FDSetRead(descriptor.GetWaitableHandle());
45+
sh.FDSetRead((lldb::socket_t)descriptor.GetWaitableHandle());
4646
Status status = sh.Select();
4747
if (status.Fail()) {
4848
// Convert timeouts into a specific error.

lldb/source/Host/common/Socket.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,7 @@ Socket::DecodeHostAndPort(llvm::StringRef host_and_port) {
313313
}
314314

315315
IOObject::WaitableHandle Socket::GetWaitableHandle() {
316-
// TODO: On Windows, use WSAEventSelect
317-
return m_socket;
316+
return (IOObject::WaitableHandle)m_socket;
318317
}
319318

320319
Status Socket::Read(void *buf, size_t &num_bytes) {

lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ size_t ConnectionFileDescriptor::Read(void *dst, size_t dst_len,
276276
"%p ConnectionFileDescriptor::Read() fd = %" PRIu64
277277
", dst = %p, dst_len = %" PRIu64 ") => %" PRIu64 ", error = %s",
278278
static_cast<void *>(this),
279-
static_cast<uint64_t>(m_io_sp->GetWaitableHandle()),
279+
static_cast<file_t>(m_io_sp->GetWaitableHandle()),
280280
static_cast<void *>(dst), static_cast<uint64_t>(dst_len),
281281
static_cast<uint64_t>(bytes_read), error.AsCString());
282282
}
@@ -380,7 +380,7 @@ size_t ConnectionFileDescriptor::Write(const void *src, size_t src_len,
380380
"%p ConnectionFileDescriptor::Write(fd = %" PRIu64
381381
", src = %p, src_len = %" PRIu64 ") => %" PRIu64 " (error = %s)",
382382
static_cast<void *>(this),
383-
static_cast<uint64_t>(m_io_sp->GetWaitableHandle()),
383+
static_cast<file_t>(m_io_sp->GetWaitableHandle()),
384384
static_cast<const void *>(src), static_cast<uint64_t>(src_len),
385385
static_cast<uint64_t>(bytes_sent), error.AsCString());
386386
}
@@ -451,7 +451,8 @@ ConnectionFileDescriptor::BytesAvailable(const Timeout<std::micro> &timeout,
451451
if (timeout)
452452
select_helper.SetTimeout(*timeout);
453453

454-
select_helper.FDSetRead(handle);
454+
// FIXME: Migrate to MainLoop.
455+
select_helper.FDSetRead((lldb::socket_t)handle);
455456
#if defined(_WIN32)
456457
// select() won't accept pipes on Windows. The entire Windows codepath
457458
// needs to be converted over to using WaitForMultipleObjects and event
@@ -493,7 +494,7 @@ ConnectionFileDescriptor::BytesAvailable(const Timeout<std::micro> &timeout,
493494
break; // Lets keep reading to until we timeout
494495
}
495496
} else {
496-
if (select_helper.FDIsSetRead(handle))
497+
if (select_helper.FDIsSetRead((lldb::socket_t)handle))
497498
return eConnectionStatusSuccess;
498499

499500
if (select_helper.FDIsSetRead(pipe_fd)) {

lldb/source/Host/windows/MainLoopWindows.cpp

Lines changed: 142 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88

99
#include "lldb/Host/windows/MainLoopWindows.h"
1010
#include "lldb/Host/Config.h"
11+
#include "lldb/Host/Socket.h"
1112
#include "lldb/Utility/Status.h"
1213
#include "llvm/Config/llvm-config.h"
14+
#include "llvm/Support/Casting.h"
15+
#include "llvm/Support/WindowsError.h"
1316
#include <algorithm>
1417
#include <cassert>
1518
#include <cerrno>
@@ -31,6 +34,122 @@ static DWORD ToTimeout(std::optional<MainLoopWindows::TimePoint> point) {
3134
return ceil<milliseconds>(dur).count();
3235
}
3336

37+
namespace {
38+
39+
class PipeEvent : public MainLoopWindows::IOEvent {
40+
public:
41+
explicit PipeEvent(HANDLE handle)
42+
: IOEvent((IOObject::WaitableHandle)CreateEventW(
43+
NULL, /*bManualReset=*/FALSE,
44+
/*bInitialState=*/FALSE, NULL)),
45+
m_handle(handle), m_ready(CreateEventW(NULL, /*bManualReset=*/FALSE,
46+
/*bInitialState=*/FALSE, NULL)) {
47+
assert(m_event && m_ready);
48+
}
49+
50+
~PipeEvent() override {
51+
if (m_monitor_thread.joinable()) {
52+
m_stopped = true;
53+
SetEvent(m_ready);
54+
// Keep trying to cancel ReadFile() until the thread exits.
55+
do {
56+
CancelIoEx((HANDLE)m_handle, /*lpOverlapped=*/NULL);
57+
} while (WaitForSingleObject(m_monitor_thread.native_handle(), 1) ==
58+
WAIT_TIMEOUT);
59+
m_monitor_thread.join();
60+
}
61+
CloseHandle((HANDLE)m_event);
62+
CloseHandle(m_ready);
63+
}
64+
65+
void WillPoll() override {
66+
if (!m_monitor_thread.joinable())
67+
m_monitor_thread = std::thread(&PipeEvent::Monitor, this);
68+
}
69+
70+
void Disarm() override { SetEvent(m_ready); }
71+
72+
/// Monitors the handle performing a zero byte read to determine when data is
73+
/// avaiable.
74+
void Monitor() {
75+
do {
76+
char buf[1];
77+
DWORD bytes_read = 0;
78+
OVERLAPPED ov = {0};
79+
// Block on a 0-byte read; this will only resume when data is
80+
// available in the pipe. The pipe must be PIPE_WAIT or this thread
81+
// will spin.
82+
BOOL success =
83+
ReadFile(m_handle, buf, /*nNumberOfBytesToRead=*/0, &bytes_read, &ov);
84+
DWORD bytes_available = 0;
85+
DWORD err = GetLastError();
86+
if (!success && err == ERROR_IO_PENDING) {
87+
success = GetOverlappedResult(m_handle, &ov, &bytes_read,
88+
/*bWait=*/TRUE);
89+
err = GetLastError();
90+
}
91+
if (success) {
92+
success =
93+
PeekNamedPipe(m_handle, NULL, 0, NULL, &bytes_available, NULL);
94+
err = GetLastError();
95+
}
96+
if (success) {
97+
if (bytes_available == 0) {
98+
// This can happen with a zero-byte write. Try again.
99+
continue;
100+
}
101+
} else if (err == ERROR_NO_DATA) {
102+
// The pipe is nonblocking. Try again.
103+
Sleep(0);
104+
continue;
105+
} else if (err == ERROR_OPERATION_ABORTED) {
106+
// Read may have been cancelled, try again.
107+
continue;
108+
}
109+
110+
SetEvent((HANDLE)m_event);
111+
112+
// Wait until the current read is consumed before doing the next read.
113+
WaitForSingleObject(m_ready, INFINITE);
114+
} while (!m_stopped);
115+
}
116+
117+
private:
118+
HANDLE m_handle;
119+
HANDLE m_ready;
120+
std::thread m_monitor_thread;
121+
std::atomic<bool> m_stopped = false;
122+
};
123+
124+
class SocketEvent : public MainLoopWindows::IOEvent {
125+
public:
126+
explicit SocketEvent(SOCKET socket)
127+
: IOEvent((IOObject::WaitableHandle)WSACreateEvent()), m_socket(socket) {
128+
assert(event != WSA_INVALID_EVENT);
129+
}
130+
131+
~SocketEvent() override { WSACloseEvent((HANDLE)m_event); }
132+
133+
void WillPoll() {
134+
int result = WSAEventSelect(m_socket, (HANDLE)m_event,
135+
FD_READ | FD_ACCEPT | FD_CLOSE);
136+
assert(result == 0);
137+
UNUSED_IF_ASSERT_DISABLED(result);
138+
}
139+
140+
void DidPoll() {
141+
int result = WSAEventSelect(m_socket, WSA_INVALID_EVENT, 0);
142+
assert(result == 0);
143+
UNUSED_IF_ASSERT_DISABLED(result);
144+
}
145+
146+
void Disarm() override { WSAResetEvent((HANDLE)m_event); }
147+
148+
SOCKET m_socket;
149+
};
150+
151+
} // namespace
152+
34153
MainLoopWindows::MainLoopWindows() {
35154
m_interrupt_event = WSACreateEvent();
36155
assert(m_interrupt_event != WSA_INVALID_EVENT);
@@ -44,26 +163,20 @@ MainLoopWindows::~MainLoopWindows() {
44163
}
45164

46165
llvm::Expected<size_t> MainLoopWindows::Poll() {
47-
std::vector<WSAEVENT> events;
166+
std::vector<HANDLE> events;
48167
events.reserve(m_read_fds.size() + 1);
49-
for (auto &[fd, info] : m_read_fds) {
50-
int result = WSAEventSelect(fd, info.event, FD_READ | FD_ACCEPT | FD_CLOSE);
51-
assert(result == 0);
52-
UNUSED_IF_ASSERT_DISABLED(result);
53-
54-
events.push_back(info.event);
168+
for (auto &[_, fd_info] : m_read_fds) {
169+
fd_info.event->WillPoll();
170+
events.push_back((HANDLE)fd_info.event->GetHandle());
55171
}
56172
events.push_back(m_interrupt_event);
57173

58174
DWORD result =
59175
WSAWaitForMultipleEvents(events.size(), events.data(), FALSE,
60176
ToTimeout(GetNextWakeupTime()), FALSE);
61177

62-
for (auto &fd : m_read_fds) {
63-
int result = WSAEventSelect(fd.first, WSA_INVALID_EVENT, 0);
64-
assert(result == 0);
65-
UNUSED_IF_ASSERT_DISABLED(result);
66-
}
178+
for (auto &[_, fd_info] : m_read_fds)
179+
fd_info.event->DidPoll();
67180

68181
if (result >= WSA_WAIT_EVENT_0 && result < WSA_WAIT_EVENT_0 + events.size())
69182
return result - WSA_WAIT_EVENT_0;
@@ -83,28 +196,25 @@ MainLoopWindows::RegisterReadObject(const IOObjectSP &object_sp,
83196
error = Status::FromErrorString("IO object is not valid.");
84197
return nullptr;
85198
}
86-
if (object_sp->GetFdType() != IOObject::eFDTypeSocket) {
87-
error = Status::FromErrorString(
88-
"MainLoopWindows: non-socket types unsupported on Windows");
89-
return nullptr;
90-
}
91199

92-
WSAEVENT event = WSACreateEvent();
93-
if (event == WSA_INVALID_EVENT) {
94-
error =
95-
Status::FromErrorStringWithFormat("Cannot create monitoring event.");
200+
IOObject::WaitableHandle waitable_handle = object_sp->GetWaitableHandle();
201+
assert(waitable_handle != IOObject::kInvalidHandleValue);
202+
203+
if (m_read_fds.find(waitable_handle) != m_read_fds.end()) {
204+
error = Status::FromErrorStringWithFormat(
205+
"File descriptor %d already monitored.", waitable_handle);
96206
return nullptr;
97207
}
98208

99-
const bool inserted =
100-
m_read_fds
101-
.try_emplace(object_sp->GetWaitableHandle(), FdInfo{event, callback})
102-
.second;
103-
if (!inserted) {
104-
WSACloseEvent(event);
105-
error = Status::FromErrorStringWithFormat(
106-
"File descriptor %d already monitored.",
107-
object_sp->GetWaitableHandle());
209+
if (object_sp->GetFdType() == IOObject::eFDTypeSocket)
210+
m_read_fds[waitable_handle] = {
211+
std::make_unique<SocketEvent>((SOCKET)waitable_handle), callback};
212+
else if (GetFileType(waitable_handle) == FILE_TYPE_PIPE)
213+
m_read_fds[waitable_handle] = {
214+
std::make_unique<PipeEvent>((HANDLE)waitable_handle), callback};
215+
else {
216+
error = Status::FromErrorStringWithFormat("Unsupported file type %d",
217+
GetFileType(waitable_handle));
108218
return nullptr;
109219
}
110220

@@ -114,18 +224,9 @@ MainLoopWindows::RegisterReadObject(const IOObjectSP &object_sp,
114224
void MainLoopWindows::UnregisterReadObject(IOObject::WaitableHandle handle) {
115225
auto it = m_read_fds.find(handle);
116226
assert(it != m_read_fds.end());
117-
BOOL result = WSACloseEvent(it->second.event);
118-
assert(result == TRUE);
119-
UNUSED_IF_ASSERT_DISABLED(result);
120227
m_read_fds.erase(it);
121228
}
122229

123-
void MainLoopWindows::ProcessReadObject(IOObject::WaitableHandle handle) {
124-
auto it = m_read_fds.find(handle);
125-
if (it != m_read_fds.end())
126-
it->second.callback(*this); // Do the work
127-
}
128-
129230
Status MainLoopWindows::Run() {
130231
m_terminate_request = false;
131232

@@ -138,8 +239,8 @@ Status MainLoopWindows::Run() {
138239

139240
if (*signaled_event < m_read_fds.size()) {
140241
auto &KV = *std::next(m_read_fds.begin(), *signaled_event);
141-
WSAResetEvent(KV.second.event);
142-
ProcessReadObject(KV.first);
242+
KV.second.event->Disarm();
243+
KV.second.callback(*this); // Do the work.
143244
} else {
144245
assert(*signaled_event == m_read_fds.size());
145246
WSAResetEvent(m_interrupt_event);

lldb/source/Utility/IOObject.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,16 @@
88

99
#include "lldb/Utility/IOObject.h"
1010

11+
#ifdef _WIN32
12+
#include "lldb/Host/windows/windows.h"
13+
#endif
14+
1115
using namespace lldb_private;
1216

17+
#ifdef _WIN32
18+
const IOObject::WaitableHandle IOObject::kInvalidHandleValue =
19+
INVALID_HANDLE_VALUE;
20+
#else
1321
const IOObject::WaitableHandle IOObject::kInvalidHandleValue = -1;
22+
#endif
1423
IOObject::~IOObject() = default;

0 commit comments

Comments
 (0)