//===-- 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/Utility/Status.h" #include "llvm/Config/llvm-config.h" #include "llvm/Support/Casting.h" #include "llvm/Support/WindowsError.h" #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((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()) 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 m_stopped = false; }; class SocketEvent : public MainLoopWindows::IOEvent { public: explicit SocketEvent(SOCKET socket) : IOEvent((IOObject::WaitableHandle)WSACreateEvent()), m_socket(socket) { assert(m_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); } 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((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_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((SOCKET)waitable_handle), callback}; else if (GetFileType(waitable_handle) == FILE_TYPE_PIPE) m_read_fds[waitable_handle] = { std::make_unique((HANDLE)waitable_handle), callback}; else { error = Status::FromErrorStringWithFormat("Unsupported file type %d", GetFileType(waitable_handle)); return nullptr; } 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); }