LLDB mainline
MainLoopWindows.cpp
Go to the documentation of this file.
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-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "lldb/Host/Config.h"
11#include "lldb/Host/Socket.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(nullptr, /*bManualReset=*/TRUE,
47 /*bInitialState=*/FALSE, nullptr)),
48 m_handle(handle),
49 m_ready(CreateEventW(nullptr, /*bManualReset=*/TRUE,
50 /*bInitialState=*/FALSE, nullptr)),
51 m_stop(CreateEventW(nullptr, /*bManualReset=*/TRUE,
52 /*bInitialState=*/FALSE, nullptr)),
53 m_read_done(CreateEventW(nullptr, /*bManualReset=*/TRUE,
54 /*bInitialState=*/FALSE, nullptr)),
55 m_exited(CreateEventW(nullptr, /*bManualReset=*/TRUE,
56 /*bInitialState=*/FALSE, nullptr)) {
57 assert(m_event && m_ready && m_stop && m_read_done && m_exited);
58 m_monitor_thread = std::thread(&PipeEvent::Monitor, this);
59 }
60
61 ~PipeEvent() override {
62 if (m_monitor_thread.joinable()) {
63 {
64 std::lock_guard<std::mutex> guard(m_mutex);
65 m_stopped = true;
66 SetEvent(m_ready);
67 SetEvent(m_stop);
68 }
69 do {
70 CancelIoEx(m_handle, &m_ov);
71 } while (WaitForSingleObject(m_exited, 1) == WAIT_TIMEOUT);
72 m_monitor_thread.join();
73 }
74 CloseHandle(m_event);
75 CloseHandle(m_ready);
76 CloseHandle(m_stop);
77 CloseHandle(m_read_done);
78 CloseHandle(m_exited);
79 }
80
81 void WillPoll() override {
82 std::lock_guard<std::mutex> guard(m_mutex);
83
84 HANDLE handles[2] = {m_event, m_ready};
85 if (WaitForMultipleObjects(2, handles, /*bWaitAll=*/FALSE,
86 /*dwMilliseconds=*/0) != WAIT_TIMEOUT) {
87 // Either:
88 // - The thread has already signalled that the data is available. No need
89 // for further polling until we consume that event.
90 // - The thread is already waiting for data to become available.
91 return;
92 }
93 // Start waiting.
94 SetEvent(m_ready);
95 }
96
97 void Disarm() override {
98 std::lock_guard<std::mutex> guard(m_mutex);
99 ResetEvent(m_event);
100 }
101
102 /// Monitors the handle performing a zero byte read to determine when data is
103 /// avaiable.
104 void Monitor() {
105 // Wait until the MainLoop tells us to start.
106 WaitForSingleObject(m_ready, INFINITE);
107
108 while (!m_stopped) {
109 char buf[1];
110 DWORD bytes_read = 0;
111 ZeroMemory(&m_ov, sizeof(m_ov));
112 m_ov.hEvent = m_read_done;
113 // Block on a 0-byte read; this will only resume when data is
114 // available in the pipe. The pipe must be PIPE_WAIT or this thread
115 // will spin.
116 BOOL success = ReadFile(m_handle, buf, /*nNumberOfBytesToRead=*/0,
117 &bytes_read, &m_ov);
118 DWORD bytes_available = 0;
119 DWORD err = GetLastError();
120 if (!success && err == ERROR_IO_PENDING) {
121 // m_stop stays signaled, so a stop requested before the read was
122 // issued is still seen here.
123 HANDLE handles[2] = {m_read_done, m_stop};
124 if (WaitForMultipleObjects(2, handles, /*bWaitAll=*/FALSE, INFINITE) !=
125 WAIT_OBJECT_0) {
126 CancelIoEx(m_handle, &m_ov);
127 GetOverlappedResult(m_handle, &m_ov, &bytes_read, /*bWait=*/TRUE);
128 break;
129 }
130 success = GetOverlappedResult(m_handle, &m_ov, &bytes_read,
131 /*bWait=*/FALSE);
132 err = GetLastError();
133 }
134 if (success) {
135 success = PeekNamedPipe(m_handle, nullptr, 0, nullptr, &bytes_available,
136 nullptr);
137 err = GetLastError();
138 }
139 if (success) {
140 if (bytes_available == 0) {
141 // This can happen with a zero-byte write. Try again.
142 continue;
143 }
144 } else if (err == ERROR_NO_DATA) {
145 // The pipe is nonblocking. Try again.
146 Sleep(0);
147 continue;
148 } else if (err == ERROR_OPERATION_ABORTED) {
149 // Read may have been cancelled, try again.
150 continue;
151 }
152 {
153 std::lock_guard<std::mutex> guard(m_mutex);
154
155 // Notify that data is available on the pipe.
156 SetEvent(m_event);
157 if (m_stopped) {
158 // The destructor might have called SetEvent(m_ready) before this
159 // block. If that's the case, ResetEvent(m_ready) will cause
160 // WaitForSingleObject to wait forever unless we break early.
161 break;
162 }
163 // Stop polling until we're told to resume.
164 ResetEvent(m_ready);
165 }
166
167 // Wait until the current read is consumed before doing the next read.
168 WaitForSingleObject(m_ready, INFINITE);
169 }
170 SetEvent(m_exited);
171 }
172
173private:
174 HANDLE m_handle;
175 HANDLE m_ready;
176 HANDLE m_stop;
177 HANDLE m_read_done;
178 HANDLE m_exited;
179 OVERLAPPED m_ov;
180 std::thread m_monitor_thread;
181 std::atomic<bool> m_stopped = false;
182 std::mutex m_mutex;
183};
184
185class SocketEvent : public MainLoopWindows::IOEvent {
186public:
187 explicit SocketEvent(SOCKET socket)
188 : IOEvent(WSACreateEvent()), m_socket(socket) {
189 assert(m_event != WSA_INVALID_EVENT);
190 }
191
192 ~SocketEvent() override { WSACloseEvent(m_event); }
193
194 void WillPoll() override {
195 int result =
196 WSAEventSelect(m_socket, m_event, FD_READ | FD_ACCEPT | FD_CLOSE);
197 assert(result == 0);
199 }
200
201 void DidPoll() override {
202 int result = WSAEventSelect(m_socket, WSA_INVALID_EVENT, 0);
203 assert(result == 0);
205 }
206
207 void Disarm() override { WSAResetEvent(m_event); }
208
209 SOCKET m_socket;
210};
211
212} // namespace
213
215 m_interrupt_event = WSACreateEvent();
216 assert(m_interrupt_event != WSA_INVALID_EVENT);
217}
218
220 assert(m_read_fds.empty());
221 BOOL result = WSACloseEvent(m_interrupt_event);
222 assert(result == TRUE);
224}
225
226llvm::Expected<size_t> MainLoopWindows::Poll() {
227 std::vector<HANDLE> events;
228 events.reserve(m_read_fds.size() + 1);
229 for (auto &[_, fd_info] : m_read_fds) {
230 fd_info.event->WillPoll();
231 events.push_back(fd_info.event->GetHandle());
232 }
233 events.push_back(m_interrupt_event);
234
235 DWORD result =
236 WSAWaitForMultipleEvents(events.size(), events.data(), FALSE,
237 ToTimeout(GetNextWakeupTime()), FALSE);
238
239 for (auto &[_, fd_info] : m_read_fds)
240 fd_info.event->DidPoll();
241
242 if (result >= WSA_WAIT_EVENT_0 && result < WSA_WAIT_EVENT_0 + events.size())
243 return result - WSA_WAIT_EVENT_0;
244
245 // A timeout is treated as a (premature) signalization of the interrupt event.
246 if (result == WSA_WAIT_TIMEOUT)
247 return events.size() - 1;
248
249 return llvm::createStringError(llvm::inconvertibleErrorCode(),
250 "WSAWaitForMultipleEvents failed");
251}
252
255 const Callback &callback, Status &error) {
256 if (!object_sp || !object_sp->IsValid()) {
257 error = Status::FromErrorString("IO object is not valid.");
258 return nullptr;
259 }
260
261 IOObject::WaitableHandle waitable_handle = object_sp->GetWaitableHandle();
262 assert(waitable_handle != IOObject::kInvalidHandleValue);
263
264 if (m_read_fds.find(waitable_handle) != m_read_fds.end()) {
266 "File descriptor %p already monitored.", waitable_handle);
267 return nullptr;
268 }
269
270 if (object_sp->GetFdType() == IOObject::eFDTypeSocket) {
271 m_read_fds[waitable_handle] = {
272 std::make_unique<SocketEvent>(
273 reinterpret_cast<SOCKET>(waitable_handle)),
274 callback};
275 } else {
276 DWORD file_type = GetFileType(waitable_handle);
277 if (file_type != FILE_TYPE_CHAR && file_type != FILE_TYPE_PIPE) {
278 error = Status::FromErrorStringWithFormat("Unsupported file type %ld",
279 file_type);
280 return nullptr;
281 }
282
283 m_read_fds[waitable_handle] = {std::make_unique<PipeEvent>(waitable_handle),
284 callback};
285 }
286
287 return CreateReadHandle(object_sp);
288}
289
291 auto it = m_read_fds.find(handle);
292 assert(it != m_read_fds.end());
293 m_read_fds.erase(it);
294}
295
297 m_terminate_request = false;
298
300
301 while (!m_terminate_request) {
302 llvm::Expected<size_t> signaled_event = Poll();
303 if (!signaled_event)
304 return Status::FromError(signaled_event.takeError());
305
306 if (*signaled_event < m_read_fds.size()) {
307 auto &KV = *std::next(m_read_fds.begin(), *signaled_event);
308 KV.second.event->Disarm();
309 KV.second.callback(*this); // Do the work.
310 } else {
311 assert(*signaled_event == m_read_fds.size());
312 WSAResetEvent(m_interrupt_event);
313 }
315 }
316 return Status();
317}
318
320 return WSASetEvent(m_interrupt_event);
321}
static llvm::raw_ostream & error(Stream &strm)
static DWORD ToTimeout(std::optional< MainLoopWindows::TimePoint > point)
void * HANDLE
static const WaitableHandle kInvalidHandleValue
Definition IOObject.h:31
lldb::file_t WaitableHandle
Definition IOObject.h:29
std::unique_ptr< ReadHandle > ReadHandleUP
std::optional< TimePoint > GetNextWakeupTime()
ReadHandleUP CreateReadHandle(const lldb::IOObjectSP &object_sp)
std::function< void(MainLoopBase &)> Callback
llvm::Expected< size_t > Poll()
llvm::DenseMap< IOObject::WaitableHandle, FdInfo > m_read_fds
bool Interrupt() override
Interrupt the loop that is currently waiting for events.
ReadHandleUP RegisterReadObject(const lldb::IOObjectSP &object_sp, const Callback &callback, Status &error) override
void UnregisterReadObject(IOObject::WaitableHandle handle) override
An error handling class.
Definition Status.h:118
static Status FromErrorStringWithFormat(const char *format,...) __attribute__((format(printf
Definition Status.cpp:106
static Status FromErrorString(const char *str)
Definition Status.h:141
static Status FromError(llvm::Error error)
Avoid using this in new code. Migrate APIs to llvm::Expected instead.
Definition Status.cpp:136
#define UNUSED_IF_ASSERT_DISABLED(x)
A class that represents a running process on the host machine.
std::shared_ptr< lldb_private::IOObject > IOObjectSP