-
Notifications
You must be signed in to change notification settings - Fork 14.4k
[lldb] Adding pipe support to lldb_private::MainLoopWindows. #145621
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
79c10b6
a935e75
0f07158
65430e3
ea1ed2b
024a7d9
3018481
d74d132
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,8 +8,11 @@ | |
|
||
#include "lldb/Host/windows/MainLoopWindows.h" | ||
#include "lldb/Host/Config.h" | ||
#include "lldb/Host/Socket.h" | ||
#include "lldb/Utility/Status.h" | ||
#include "llvm/Config/llvm-config.h" | ||
#include "llvm/Support/Casting.h" | ||
#include "llvm/Support/WindowsError.h" | ||
#include <algorithm> | ||
#include <cassert> | ||
#include <cerrno> | ||
|
@@ -31,6 +34,122 @@ static DWORD ToTimeout(std::optional<MainLoopWindows::TimePoint> point) { | |
return ceil<milliseconds>(dur).count(); | ||
} | ||
|
||
namespace { | ||
|
||
class PipeEvent : public MainLoopWindows::IOEvent { | ||
public: | ||
explicit PipeEvent(HANDLE handle) | ||
: IOEvent((IOObject::WaitableHandle)CreateEventW( | ||
NULL, /*bManualReset=*/FALSE, | ||
/*bInitialState=*/FALSE, NULL)), | ||
m_handle(handle), m_ready(CreateEventW(NULL, /*bManualReset=*/FALSE, | ||
/*bInitialState=*/FALSE, NULL)) { | ||
assert(m_event && m_ready); | ||
} | ||
|
||
~PipeEvent() override { | ||
if (m_monitor_thread.joinable()) { | ||
m_stopped = true; | ||
SetEvent(m_ready); | ||
// Keep trying to cancel ReadFile() until the thread exits. | ||
do { | ||
CancelIoEx((HANDLE)m_handle, /*lpOverlapped=*/NULL); | ||
} while (WaitForSingleObject(m_monitor_thread.native_handle(), 1) == | ||
WAIT_TIMEOUT); | ||
m_monitor_thread.join(); | ||
} | ||
CloseHandle((HANDLE)m_event); | ||
CloseHandle(m_ready); | ||
} | ||
|
||
void WillPoll() override { | ||
if (!m_monitor_thread.joinable()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would you say to creating the thread in the constructor? It would remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had this at first but I needed to have an extra ivar for starting the signaling the thread to start in the 'WillPoll', otherwise if you registered a PipeEvent and didn't immediately run the loop it potentially prematurely get a read signal. I tried using the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, I see what you mean. Looks good then. |
||
m_monitor_thread = std::thread(&PipeEvent::Monitor, this); | ||
} | ||
|
||
void Disarm() override { SetEvent(m_ready); } | ||
|
||
/// Monitors the handle performing a zero byte read to determine when data is | ||
/// avaiable. | ||
void Monitor() { | ||
do { | ||
char buf[1]; | ||
DWORD bytes_read = 0; | ||
OVERLAPPED ov = {0}; | ||
// Block on a 0-byte read; this will only resume when data is | ||
// available in the pipe. The pipe must be PIPE_WAIT or this thread | ||
// will spin. | ||
BOOL success = | ||
ReadFile(m_handle, buf, /*nNumberOfBytesToRead=*/0, &bytes_read, &ov); | ||
DWORD bytes_available = 0; | ||
DWORD err = GetLastError(); | ||
if (!success && err == ERROR_IO_PENDING) { | ||
success = GetOverlappedResult(m_handle, &ov, &bytes_read, | ||
/*bWait=*/TRUE); | ||
err = GetLastError(); | ||
} | ||
if (success) { | ||
success = | ||
PeekNamedPipe(m_handle, NULL, 0, NULL, &bytes_available, NULL); | ||
err = GetLastError(); | ||
} | ||
if (success) { | ||
if (bytes_available == 0) { | ||
// This can happen with a zero-byte write. Try again. | ||
continue; | ||
} | ||
} else if (err == ERROR_NO_DATA) { | ||
// The pipe is nonblocking. Try again. | ||
Sleep(0); | ||
continue; | ||
} else if (err == ERROR_OPERATION_ABORTED) { | ||
// Read may have been cancelled, try again. | ||
continue; | ||
} | ||
|
||
SetEvent((HANDLE)m_event); | ||
|
||
// Wait until the current read is consumed before doing the next read. | ||
WaitForSingleObject(m_ready, INFINITE); | ||
} while (!m_stopped); | ||
} | ||
|
||
private: | ||
HANDLE m_handle; | ||
HANDLE m_ready; | ||
std::thread m_monitor_thread; | ||
std::atomic<bool> m_stopped = false; | ||
}; | ||
|
||
class SocketEvent : public MainLoopWindows::IOEvent { | ||
public: | ||
explicit SocketEvent(SOCKET socket) | ||
: IOEvent((IOObject::WaitableHandle)WSACreateEvent()), m_socket(socket) { | ||
assert(event != WSA_INVALID_EVENT); | ||
} | ||
|
||
~SocketEvent() override { WSACloseEvent((HANDLE)m_event); } | ||
|
||
void WillPoll() { | ||
int result = WSAEventSelect(m_socket, (HANDLE)m_event, | ||
FD_READ | FD_ACCEPT | FD_CLOSE); | ||
assert(result == 0); | ||
UNUSED_IF_ASSERT_DISABLED(result); | ||
} | ||
|
||
void DidPoll() { | ||
int result = WSAEventSelect(m_socket, WSA_INVALID_EVENT, 0); | ||
assert(result == 0); | ||
UNUSED_IF_ASSERT_DISABLED(result); | ||
} | ||
|
||
void Disarm() override { WSAResetEvent((HANDLE)m_event); } | ||
|
||
SOCKET m_socket; | ||
}; | ||
|
||
} // namespace | ||
|
||
MainLoopWindows::MainLoopWindows() { | ||
m_interrupt_event = WSACreateEvent(); | ||
assert(m_interrupt_event != WSA_INVALID_EVENT); | ||
|
@@ -44,26 +163,20 @@ MainLoopWindows::~MainLoopWindows() { | |
} | ||
|
||
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_info] : m_read_fds) { | ||
fd_info.event->WillPoll(); | ||
events.push_back((HANDLE)fd_info.event->GetHandle()); | ||
} | ||
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); | ||
} | ||
for (auto &[_, fd_info] : m_read_fds) | ||
fd_info.event->DidPoll(); | ||
|
||
if (result >= WSA_WAIT_EVENT_0 && result < WSA_WAIT_EVENT_0 + events.size()) | ||
return result - WSA_WAIT_EVENT_0; | ||
|
@@ -83,28 +196,25 @@ 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."); | ||
IOObject::WaitableHandle waitable_handle = object_sp->GetWaitableHandle(); | ||
assert(waitable_handle != IOObject::kInvalidHandleValue); | ||
|
||
if (m_read_fds.find(waitable_handle) != m_read_fds.end()) { | ||
error = Status::FromErrorStringWithFormat( | ||
"File descriptor %d already monitored.", waitable_handle); | ||
return nullptr; | ||
} | ||
|
||
const bool inserted = | ||
m_read_fds | ||
.try_emplace(object_sp->GetWaitableHandle(), FdInfo{event, callback}) | ||
.second; | ||
if (!inserted) { | ||
WSACloseEvent(event); | ||
error = Status::FromErrorStringWithFormat( | ||
"File descriptor %d already monitored.", | ||
object_sp->GetWaitableHandle()); | ||
if (object_sp->GetFdType() == IOObject::eFDTypeSocket) | ||
m_read_fds[waitable_handle] = { | ||
std::make_unique<SocketEvent>((SOCKET)waitable_handle), callback}; | ||
else if (GetFileType(waitable_handle) == FILE_TYPE_PIPE) | ||
m_read_fds[waitable_handle] = { | ||
std::make_unique<PipeEvent>((HANDLE)waitable_handle), callback}; | ||
else { | ||
error = Status::FromErrorStringWithFormat("Unsupported file type %d", | ||
GetFileType(waitable_handle)); | ||
return nullptr; | ||
} | ||
|
||
|
@@ -114,18 +224,9 @@ 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); | ||
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 +239,8 @@ 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); | ||
KV.second.event->Disarm(); | ||
KV.second.callback(*this); // Do the work. | ||
} else { | ||
assert(*signaled_event == m_read_fds.size()); | ||
WSAResetEvent(m_interrupt_event); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you change these from uint64_t to file_t? It's now inconsistent with the format string above, which uses PRIu64.
lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp:279:15: error: format specifies type 'unsigned long' but the argument has type 'file_t' (aka 'int') [-Werror,-Wformat]
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll fix that, I was thought that
lldb::file_t
was the more universal type to use for these handles.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this was already fixed by 98e6d5c