278 lines · cpp
1//===-- MainLoopWindows.cpp -----------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "lldb/Host/windows/MainLoopWindows.h"10#include "lldb/Host/Config.h"11#include "lldb/Host/Socket.h"12#include "lldb/Host/windows/windows.h"13#include "lldb/Utility/Status.h"14#include "llvm/Config/llvm-config.h"15#include "llvm/Support/WindowsError.h"16#include <algorithm>17#include <atomic>18#include <cassert>19#include <ctime>20#include <io.h>21#include <synchapi.h>22#include <thread>23#include <vector>24#include <winbase.h>25#include <winerror.h>26#include <winsock2.h>27 28using namespace lldb;29using namespace lldb_private;30 31static DWORD ToTimeout(std::optional<MainLoopWindows::TimePoint> point) {32 using namespace std::chrono;33 34 if (!point)35 return WSA_INFINITE;36 37 nanoseconds dur = (std::max)(*point - steady_clock::now(), nanoseconds(0));38 return ceil<milliseconds>(dur).count();39}40 41namespace {42 43class PipeEvent : public MainLoopWindows::IOEvent {44public:45 explicit PipeEvent(HANDLE handle)46 : IOEvent(CreateEventW(NULL, /*bManualReset=*/TRUE,47 /*bInitialState=*/FALSE, NULL)),48 m_handle(handle), m_ready(CreateEventW(NULL, /*bManualReset=*/TRUE,49 /*bInitialState=*/FALSE, NULL)) {50 assert(m_event && m_ready);51 m_monitor_thread = std::thread(&PipeEvent::Monitor, this);52 }53 54 ~PipeEvent() override {55 if (m_monitor_thread.joinable()) {56 m_stopped = true;57 SetEvent(m_ready);58 CancelIoEx(m_handle, /*lpOverlapped=*/NULL);59 m_monitor_thread.join();60 }61 CloseHandle(m_event);62 CloseHandle(m_ready);63 }64 65 void WillPoll() override {66 if (WaitForSingleObject(m_event, /*dwMilliseconds=*/0) != WAIT_TIMEOUT) {67 // The thread has already signalled that the data is available. No need68 // for further polling until we consume that event.69 return;70 }71 if (WaitForSingleObject(m_ready, /*dwMilliseconds=*/0) != WAIT_TIMEOUT) {72 // The thread is already waiting for data to become available.73 return;74 }75 // Start waiting.76 SetEvent(m_ready);77 }78 79 void Disarm() override { ResetEvent(m_event); }80 81 /// Monitors the handle performing a zero byte read to determine when data is82 /// avaiable.83 void Monitor() {84 // Wait until the MainLoop tells us to start.85 WaitForSingleObject(m_ready, INFINITE);86 87 do {88 char buf[1];89 DWORD bytes_read = 0;90 OVERLAPPED ov;91 ZeroMemory(&ov, sizeof(ov));92 // Block on a 0-byte read; this will only resume when data is93 // available in the pipe. The pipe must be PIPE_WAIT or this thread94 // will spin.95 BOOL success =96 ReadFile(m_handle, buf, /*nNumberOfBytesToRead=*/0, &bytes_read, &ov);97 DWORD bytes_available = 0;98 DWORD err = GetLastError();99 if (!success && err == ERROR_IO_PENDING) {100 success = GetOverlappedResult(m_handle, &ov, &bytes_read,101 /*bWait=*/TRUE);102 err = GetLastError();103 }104 if (success) {105 success =106 PeekNamedPipe(m_handle, NULL, 0, NULL, &bytes_available, NULL);107 err = GetLastError();108 }109 if (success) {110 if (bytes_available == 0) {111 // This can happen with a zero-byte write. Try again.112 continue;113 }114 } else if (err == ERROR_NO_DATA) {115 // The pipe is nonblocking. Try again.116 Sleep(0);117 continue;118 } else if (err == ERROR_OPERATION_ABORTED) {119 // Read may have been cancelled, try again.120 continue;121 }122 123 // Notify that data is available on the pipe. It's important to set this124 // before clearing m_ready to avoid a race with WillPoll.125 SetEvent(m_event);126 // Stop polling until we're told to resume.127 ResetEvent(m_ready);128 129 // Wait until the current read is consumed before doing the next read.130 WaitForSingleObject(m_ready, INFINITE);131 } while (!m_stopped);132 }133 134private:135 HANDLE m_handle;136 HANDLE m_ready;137 std::thread m_monitor_thread;138 std::atomic<bool> m_stopped = false;139};140 141class SocketEvent : public MainLoopWindows::IOEvent {142public:143 explicit SocketEvent(SOCKET socket)144 : IOEvent(WSACreateEvent()), m_socket(socket) {145 assert(m_event != WSA_INVALID_EVENT);146 }147 148 ~SocketEvent() override { WSACloseEvent(m_event); }149 150 void WillPoll() override {151 int result =152 WSAEventSelect(m_socket, m_event, FD_READ | FD_ACCEPT | FD_CLOSE);153 assert(result == 0);154 UNUSED_IF_ASSERT_DISABLED(result);155 }156 157 void DidPoll() override {158 int result = WSAEventSelect(m_socket, WSA_INVALID_EVENT, 0);159 assert(result == 0);160 UNUSED_IF_ASSERT_DISABLED(result);161 }162 163 void Disarm() override { WSAResetEvent(m_event); }164 165 SOCKET m_socket;166};167 168} // namespace169 170MainLoopWindows::MainLoopWindows() {171 m_interrupt_event = WSACreateEvent();172 assert(m_interrupt_event != WSA_INVALID_EVENT);173}174 175MainLoopWindows::~MainLoopWindows() {176 assert(m_read_fds.empty());177 BOOL result = WSACloseEvent(m_interrupt_event);178 assert(result == TRUE);179 UNUSED_IF_ASSERT_DISABLED(result);180}181 182llvm::Expected<size_t> MainLoopWindows::Poll() {183 std::vector<HANDLE> events;184 events.reserve(m_read_fds.size() + 1);185 for (auto &[_, fd_info] : m_read_fds) {186 fd_info.event->WillPoll();187 events.push_back(fd_info.event->GetHandle());188 }189 events.push_back(m_interrupt_event);190 191 DWORD result =192 WSAWaitForMultipleEvents(events.size(), events.data(), FALSE,193 ToTimeout(GetNextWakeupTime()), FALSE);194 195 for (auto &[_, fd_info] : m_read_fds)196 fd_info.event->DidPoll();197 198 if (result >= WSA_WAIT_EVENT_0 && result < WSA_WAIT_EVENT_0 + events.size())199 return result - WSA_WAIT_EVENT_0;200 201 // A timeout is treated as a (premature) signalization of the interrupt event.202 if (result == WSA_WAIT_TIMEOUT)203 return events.size() - 1;204 205 return llvm::createStringError(llvm::inconvertibleErrorCode(),206 "WSAWaitForMultipleEvents failed");207}208 209MainLoopWindows::ReadHandleUP210MainLoopWindows::RegisterReadObject(const IOObjectSP &object_sp,211 const Callback &callback, Status &error) {212 if (!object_sp || !object_sp->IsValid()) {213 error = Status::FromErrorString("IO object is not valid.");214 return nullptr;215 }216 217 IOObject::WaitableHandle waitable_handle = object_sp->GetWaitableHandle();218 assert(waitable_handle != IOObject::kInvalidHandleValue);219 220 if (m_read_fds.find(waitable_handle) != m_read_fds.end()) {221 error = Status::FromErrorStringWithFormat(222 "File descriptor %p already monitored.", waitable_handle);223 return nullptr;224 }225 226 if (object_sp->GetFdType() == IOObject::eFDTypeSocket) {227 m_read_fds[waitable_handle] = {228 std::make_unique<SocketEvent>(229 reinterpret_cast<SOCKET>(waitable_handle)),230 callback};231 } else {232 DWORD file_type = GetFileType(waitable_handle);233 if (file_type != FILE_TYPE_PIPE) {234 error = Status::FromErrorStringWithFormat("Unsupported file type %ld",235 file_type);236 return nullptr;237 }238 239 m_read_fds[waitable_handle] = {std::make_unique<PipeEvent>(waitable_handle),240 callback};241 }242 243 return CreateReadHandle(object_sp);244}245 246void MainLoopWindows::UnregisterReadObject(IOObject::WaitableHandle handle) {247 auto it = m_read_fds.find(handle);248 assert(it != m_read_fds.end());249 m_read_fds.erase(it);250}251 252Status MainLoopWindows::Run() {253 m_terminate_request = false;254 255 Status error;256 257 while (!m_terminate_request) {258 llvm::Expected<size_t> signaled_event = Poll();259 if (!signaled_event)260 return Status::FromError(signaled_event.takeError());261 262 if (*signaled_event < m_read_fds.size()) {263 auto &KV = *std::next(m_read_fds.begin(), *signaled_event);264 KV.second.event->Disarm();265 KV.second.callback(*this); // Do the work.266 } else {267 assert(*signaled_event == m_read_fds.size());268 WSAResetEvent(m_interrupt_event);269 }270 ProcessCallbacks();271 }272 return Status();273}274 275bool MainLoopWindows::Interrupt() {276 return WSASetEvent(m_interrupt_event);277}278