//===-- MainLoopWindows.cpp -----------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "lldb/Host/windows/MainLoopWindows.h" #include "lldb/Host/Config.h" #include "lldb/Host/Socket.h" #include "lldb/Host/windows/windows.h" #include "lldb/Utility/Status.h" #include "llvm/Config/llvm-config.h" #include "llvm/Support/WindowsError.h" #include #include #include #include #include #include #include #include #include #include #include using namespace lldb; using namespace lldb_private; static DWORD ToTimeout(std::optional point) { using namespace std::chrono; if (!point) return WSA_INFINITE; nanoseconds dur = (std::max)(*point - steady_clock::now(), nanoseconds(0)); return ceil(dur).count(); } namespace { class PipeEvent : public MainLoopWindows::IOEvent { public: explicit PipeEvent(HANDLE handle) : IOEvent(CreateEventW(NULL, /*bManualReset=*/TRUE, /*bInitialState=*/FALSE, NULL)), m_handle(handle), m_ready(CreateEventW(NULL, /*bManualReset=*/TRUE, /*bInitialState=*/FALSE, NULL)) { assert(m_event && m_ready); m_monitor_thread = std::thread(&PipeEvent::Monitor, this); } ~PipeEvent() override { if (m_monitor_thread.joinable()) { m_stopped = true; SetEvent(m_ready); // Keep trying to cancel ReadFile() until the thread exits. do { CancelIoEx(m_handle, /*lpOverlapped=*/NULL); } while (WaitForSingleObject(m_monitor_thread.native_handle(), 1) == WAIT_TIMEOUT); m_monitor_thread.join(); } CloseHandle(m_event); CloseHandle(m_ready); } void WillPoll() override { if (WaitForSingleObject(m_event, /*dwMilliseconds=*/0) != WAIT_TIMEOUT) { // The thread has already signalled that the data is available. No need // for further polling until we consume that event. return; } if (WaitForSingleObject(m_ready, /*dwMilliseconds=*/0) != WAIT_TIMEOUT) { // The thread is already waiting for data to become available. return; } // Start waiting. SetEvent(m_ready); } void Disarm() override { ResetEvent(m_event); } /// Monitors the handle performing a zero byte read to determine when data is /// avaiable. void Monitor() { // Wait until the MainLoop tells us to start. WaitForSingleObject(m_ready, INFINITE); do { char buf[1]; DWORD bytes_read = 0; OVERLAPPED ov; ZeroMemory(&ov, sizeof(ov)); // 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; } // Notify that data is available on the pipe. It's important to set this // before clearing m_ready to avoid a race with WillPoll. SetEvent(m_event); // Stop polling until we're told to resume. ResetEvent(m_ready); // 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 m_stopped = false; }; class SocketEvent : public MainLoopWindows::IOEvent { public: explicit SocketEvent(SOCKET socket) : IOEvent(WSACreateEvent()), m_socket(socket) { assert(m_event != WSA_INVALID_EVENT); } ~SocketEvent() override { WSACloseEvent(m_event); } void WillPoll() override { int result = WSAEventSelect(m_socket, m_event, FD_READ | FD_ACCEPT | FD_CLOSE); assert(result == 0); UNUSED_IF_ASSERT_DISABLED(result); } void DidPoll() override { int result = WSAEventSelect(m_socket, WSA_INVALID_EVENT, 0); assert(result == 0); UNUSED_IF_ASSERT_DISABLED(result); } void Disarm() override { WSAResetEvent(m_event); } SOCKET m_socket; }; } // namespace MainLoopWindows::MainLoopWindows() { m_interrupt_event = WSACreateEvent(); assert(m_interrupt_event != WSA_INVALID_EVENT); } MainLoopWindows::~MainLoopWindows() { assert(m_read_fds.empty()); BOOL result = WSACloseEvent(m_interrupt_event); assert(result == TRUE); UNUSED_IF_ASSERT_DISABLED(result); } llvm::Expected MainLoopWindows::Poll() { std::vector events; events.reserve(m_read_fds.size() + 1); for (auto &[_, fd_info] : m_read_fds) { fd_info.event->WillPoll(); events.push_back(fd_info.event->GetHandle()); } events.push_back(m_interrupt_event); DWORD result = WSAWaitForMultipleEvents(events.size(), events.data(), FALSE, ToTimeout(GetNextWakeupTime()), FALSE); 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; // A timeout is treated as a (premature) signalization of the interrupt event. if (result == WSA_WAIT_TIMEOUT) return events.size() - 1; return llvm::createStringError(llvm::inconvertibleErrorCode(), "WSAWaitForMultipleEvents failed"); } MainLoopWindows::ReadHandleUP MainLoopWindows::RegisterReadObject(const IOObjectSP &object_sp, const Callback &callback, Status &error) { if (!object_sp || !object_sp->IsValid()) { error = Status::FromErrorString("IO object is not valid."); return nullptr; } 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; } if (object_sp->GetFdType() == IOObject::eFDTypeSocket) { m_read_fds[waitable_handle] = { std::make_unique( reinterpret_cast(waitable_handle)), callback}; } else { DWORD file_type = GetFileType(waitable_handle); if (file_type != FILE_TYPE_PIPE) { error = Status::FromErrorStringWithFormat("Unsupported file type %d", file_type); return nullptr; } m_read_fds[waitable_handle] = {std::make_unique(waitable_handle), callback}; } return CreateReadHandle(object_sp); } void MainLoopWindows::UnregisterReadObject(IOObject::WaitableHandle handle) { auto it = m_read_fds.find(handle); assert(it != m_read_fds.end()); m_read_fds.erase(it); } Status MainLoopWindows::Run() { m_terminate_request = false; Status error; while (!m_terminate_request) { llvm::Expected signaled_event = Poll(); if (!signaled_event) return Status::FromError(signaled_event.takeError()); if (*signaled_event < m_read_fds.size()) { auto &KV = *std::next(m_read_fds.begin(), *signaled_event); KV.second.event->Disarm(); KV.second.callback(*this); // Do the work. } else { assert(*signaled_event == m_read_fds.size()); WSAResetEvent(m_interrupt_event); } ProcessCallbacks(); } return Status(); } void MainLoopWindows::Interrupt() { WSASetEvent(m_interrupt_event); }