1//===-- MainLoopTest.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
9#include "lldb/Host/MainLoop.h"
10#include "TestingSupport/SubsystemRAII.h"
11#include "lldb/Host/ConnectionFileDescriptor.h"
12#include "lldb/Host/FileSystem.h"
13#include "lldb/Host/MainLoopBase.h"
14#include "lldb/Host/PseudoTerminal.h"
15#include "lldb/Host/common/TCPSocket.h"
16#include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX
17#include "llvm/Testing/Support/Error.h"
18#include "gtest/gtest.h"
19#include <chrono>
20#include <future>
21#include <thread>
22
23using namespace lldb_private;
24
25namespace {
26class MainLoopTest : public testing::Test {
27public:
28 SubsystemRAII<FileSystem, Socket> subsystems;
29
30 void SetUp() override {
31 Status error;
32 auto listen_socket_up = std::make_unique<TCPSocket>(args: true);
33 ASSERT_TRUE(error.Success());
34 error = listen_socket_up->Listen(name: "localhost:0", backlog: 5);
35 ASSERT_TRUE(error.Success());
36
37 Socket *accept_socket;
38 auto connect_socket_up = std::make_unique<TCPSocket>(args: true);
39 error = connect_socket_up->Connect(
40 name: llvm::formatv(Fmt: "localhost:{0}", Vals: listen_socket_up->GetLocalPortNumber())
41 .str());
42 ASSERT_TRUE(error.Success());
43 ASSERT_TRUE(listen_socket_up->Accept(std::chrono::seconds(1), accept_socket)
44 .Success());
45
46 callback_count = 0;
47 socketpair[0] = std::move(connect_socket_up);
48 socketpair[1].reset(p: accept_socket);
49 }
50
51 void TearDown() override {
52 socketpair[0].reset();
53 socketpair[1].reset();
54 }
55
56protected:
57 MainLoop::Callback make_callback() {
58 return [&](MainLoopBase &loop) {
59 ++callback_count;
60 loop.RequestTermination();
61 };
62 }
63 std::shared_ptr<Socket> socketpair[2];
64 unsigned callback_count;
65};
66} // namespace
67
68TEST_F(MainLoopTest, ReadSocketObject) {
69 char X = 'X';
70 size_t len = sizeof(X);
71 ASSERT_TRUE(socketpair[0]->Write(&X, len).Success());
72
73 MainLoop loop;
74
75 Status error;
76 auto handle = loop.RegisterReadObject(object_sp: socketpair[1], callback: make_callback(), error);
77 ASSERT_TRUE(error.Success());
78 ASSERT_TRUE(handle);
79 ASSERT_TRUE(loop.Run().Success());
80 ASSERT_EQ(1u, callback_count);
81}
82
83TEST_F(MainLoopTest, ReadPipeObject) {
84 Pipe pipe;
85
86 ASSERT_TRUE(pipe.CreateNew().Success());
87
88 MainLoop loop;
89
90 char X = 'X';
91 size_t len = sizeof(X);
92 ASSERT_THAT_EXPECTED(pipe.Write(&X, len), llvm::HasValue(1));
93
94 Status error;
95 auto handle = loop.RegisterReadObject(
96 object_sp: std::make_shared<NativeFile>(args: pipe.GetReadFileDescriptor(),
97 args: File::eOpenOptionReadOnly, args: false),
98 callback: make_callback(), error);
99 ASSERT_TRUE(error.Success());
100 ASSERT_TRUE(handle);
101 ASSERT_TRUE(loop.Run().Success());
102 ASSERT_EQ(1u, callback_count);
103}
104
105TEST_F(MainLoopTest, MultipleReadsPipeObject) {
106 Pipe pipe;
107
108 ASSERT_TRUE(pipe.CreateNew().Success());
109
110 MainLoop loop;
111
112 std::future<void> async_writer = std::async(policy: std::launch::async, fn: [&] {
113 for (int i = 0; i < 5; ++i) {
114 std::this_thread::sleep_for(rtime: std::chrono::milliseconds(500));
115 char X = 'X';
116 size_t len = sizeof(X);
117 ASSERT_THAT_EXPECTED(pipe.Write(&X, len), llvm::HasValue(1));
118 }
119 });
120
121 Status error;
122 lldb::FileSP file = std::make_shared<NativeFile>(
123 args: pipe.GetReadFileDescriptor(), args: File::eOpenOptionReadOnly, args: false);
124 auto handle = loop.RegisterReadObject(
125 object_sp: file,
126 callback: [&](MainLoopBase &loop) {
127 callback_count++;
128 if (callback_count == 5)
129 loop.RequestTermination();
130
131 // Read some data to ensure the handle is not in a readable state.
132 char buf[1024] = {0};
133 size_t len = sizeof(buf);
134 ASSERT_THAT_ERROR(file->Read(buf, len).ToError(), llvm::Succeeded());
135 EXPECT_EQ(len, 1U);
136 EXPECT_EQ(buf[0], 'X');
137 },
138 error);
139 ASSERT_TRUE(error.Success());
140 ASSERT_TRUE(handle);
141 ASSERT_TRUE(loop.Run().Success());
142 ASSERT_EQ(5u, callback_count);
143 async_writer.wait();
144}
145
146TEST_F(MainLoopTest, PipeDelayBetweenRegisterAndRun) {
147 Pipe pipe;
148
149 ASSERT_TRUE(pipe.CreateNew().Success());
150
151 MainLoop loop;
152
153 Status error;
154 lldb::FileSP file = std::make_shared<NativeFile>(
155 args: pipe.GetReadFileDescriptor(), args: File::eOpenOptionReadOnly, args: false);
156 auto handle = loop.RegisterReadObject(
157 object_sp: file,
158 callback: [&](MainLoopBase &loop) {
159 callback_count++;
160
161 // Read some data to ensure the handle is not in a readable state.
162 char buf[1024] = {0};
163 size_t len = sizeof(buf);
164 ASSERT_THAT_ERROR(file->Read(buf, len).ToError(), llvm::Succeeded());
165 EXPECT_EQ(len, 2U);
166 EXPECT_EQ(buf[0], 'X');
167 EXPECT_EQ(buf[1], 'X');
168 },
169 error);
170 auto cb = [&](MainLoopBase &) {
171 callback_count++;
172 char X = 'X';
173 size_t len = sizeof(X);
174 // Write twice and ensure we coalesce into a single read.
175 ASSERT_THAT_EXPECTED(pipe.Write(&X, len), llvm::HasValue(1));
176 ASSERT_THAT_EXPECTED(pipe.Write(&X, len), llvm::HasValue(1));
177 };
178 // Add a write that triggers a read events.
179 loop.AddCallback(callback: cb, delay: std::chrono::milliseconds(500));
180 loop.AddCallback(callback: [](MainLoopBase &loop) { loop.RequestTermination(); },
181 delay: std::chrono::milliseconds(1000));
182 ASSERT_TRUE(error.Success());
183 ASSERT_TRUE(handle);
184
185 // Write between RegisterReadObject / Run should NOT invoke the callback.
186 cb(loop);
187 ASSERT_EQ(1u, callback_count);
188
189 ASSERT_TRUE(loop.Run().Success());
190 ASSERT_EQ(4u, callback_count);
191}
192
193TEST_F(MainLoopTest, NoSelfTriggersDuringPipeHandler) {
194 Pipe pipe;
195
196 ASSERT_TRUE(pipe.CreateNew().Success());
197
198 MainLoop loop;
199
200 Status error;
201 lldb::FileSP file = std::make_shared<NativeFile>(
202 args: pipe.GetReadFileDescriptor(), args: File::eOpenOptionReadOnly, args: false);
203 auto handle = loop.RegisterReadObject(
204 object_sp: file,
205 callback: [&](MainLoopBase &lop) {
206 callback_count++;
207
208 char X = 'Y';
209 size_t len = sizeof(X);
210 // writes / reads during the handler callback should NOT trigger itself.
211 ASSERT_THAT_EXPECTED(pipe.Write(&X, len), llvm::HasValue(1));
212
213 char buf[1024] = {0};
214 len = sizeof(buf);
215 ASSERT_THAT_ERROR(file->Read(buf, len).ToError(), llvm::Succeeded());
216 EXPECT_EQ(len, 2U);
217 EXPECT_EQ(buf[0], 'X');
218 EXPECT_EQ(buf[1], 'Y');
219
220 if (callback_count == 2)
221 loop.RequestTermination();
222 },
223 error);
224 // Add a write that triggers a read event.
225 loop.AddPendingCallback(callback: [&](MainLoopBase &) {
226 char X = 'X';
227 size_t len = sizeof(X);
228 ASSERT_THAT_EXPECTED(pipe.Write(&X, len), llvm::HasValue(1));
229 });
230 loop.AddCallback(
231 callback: [&](MainLoopBase &) {
232 char X = 'X';
233 size_t len = sizeof(X);
234 ASSERT_THAT_EXPECTED(pipe.Write(&X, len), llvm::HasValue(1));
235 },
236 delay: std::chrono::milliseconds(500));
237 ASSERT_TRUE(error.Success());
238 ASSERT_TRUE(handle);
239 ASSERT_TRUE(loop.Run().Success());
240 ASSERT_EQ(2u, callback_count);
241}
242
243TEST_F(MainLoopTest, NoSpuriousPipeReads) {
244 Pipe pipe;
245
246 ASSERT_TRUE(pipe.CreateNew().Success());
247
248 char X = 'X';
249 size_t len = sizeof(X);
250 ASSERT_THAT_EXPECTED(pipe.Write(&X, len), llvm::Succeeded());
251
252 lldb::IOObjectSP r = std::make_shared<NativeFile>(
253 args: pipe.GetReadFileDescriptor(), args: File::eOpenOptionReadOnly, args: false);
254
255 MainLoop loop;
256
257 Status error;
258 auto handle = loop.RegisterReadObject(
259 object_sp: r,
260 callback: [&](MainLoopBase &) {
261 if (callback_count == 0) {
262 // Read the byte back the first time we're called. After that, the
263 // pipe is empty, and we should not be called anymore.
264 char X;
265 size_t len = sizeof(X);
266 ASSERT_THAT_ERROR(r->Read(&X, len).ToError(), llvm::Succeeded());
267 EXPECT_EQ(len, sizeof(X));
268 EXPECT_EQ(X, 'X');
269 }
270 ++callback_count;
271 },
272 error);
273 ASSERT_THAT_ERROR(error.ToError(), llvm::Succeeded());
274 // Terminate the loop after one second.
275 loop.AddCallback(callback: [](MainLoopBase &loop) { loop.RequestTermination(); },
276 delay: std::chrono::seconds(1));
277 ASSERT_THAT_ERROR(loop.Run().ToError(), llvm::Succeeded());
278
279 // Make sure the callback was called only once.
280 ASSERT_EQ(1u, callback_count);
281}
282
283TEST_F(MainLoopTest, NoSpuriousSocketReads) {
284 // Write one byte into the socket.
285 char X = 'X';
286 size_t len = sizeof(X);
287 ASSERT_TRUE(socketpair[0]->Write(&X, len).Success());
288
289 MainLoop loop;
290
291 Status error;
292 auto handle = loop.RegisterReadObject(
293 object_sp: socketpair[1],
294 callback: [this](MainLoopBase &) {
295 if (callback_count == 0) {
296 // Read the byte back the first time we're called. After that, the
297 // socket is empty, and we should not be called anymore.
298 char X;
299 size_t len = sizeof(X);
300 EXPECT_THAT_ERROR(socketpair[1]->Read(&X, len).ToError(),
301 llvm::Succeeded());
302 EXPECT_EQ(len, sizeof(X));
303 EXPECT_EQ(X, 'X');
304 }
305 ++callback_count;
306 },
307 error);
308 ASSERT_THAT_ERROR(error.ToError(), llvm::Succeeded());
309 // Terminate the loop after one second.
310 loop.AddCallback(callback: [](MainLoopBase &loop) { loop.RequestTermination(); },
311 delay: std::chrono::seconds(1));
312 ASSERT_THAT_ERROR(loop.Run().ToError(), llvm::Succeeded());
313
314 // Make sure the callback was called only once.
315 ASSERT_EQ(1u, callback_count);
316}
317
318TEST_F(MainLoopTest, TerminatesImmediately) {
319 char X = 'X';
320 size_t len = sizeof(X);
321 ASSERT_TRUE(socketpair[0]->Write(&X, len).Success());
322 ASSERT_TRUE(socketpair[1]->Write(&X, len).Success());
323
324 MainLoop loop;
325 Status error;
326 auto handle0 = loop.RegisterReadObject(object_sp: socketpair[0], callback: make_callback(), error);
327 ASSERT_TRUE(error.Success());
328 auto handle1 = loop.RegisterReadObject(object_sp: socketpair[1], callback: make_callback(), error);
329 ASSERT_TRUE(error.Success());
330
331 ASSERT_TRUE(loop.Run().Success());
332 ASSERT_EQ(1u, callback_count);
333}
334
335TEST_F(MainLoopTest, PendingCallback) {
336 char X = 'X';
337 size_t len = sizeof(X);
338 ASSERT_TRUE(socketpair[0]->Write(&X, len).Success());
339
340 MainLoop loop;
341 Status error;
342 auto handle = loop.RegisterReadObject(
343 object_sp: socketpair[1],
344 callback: [&](MainLoopBase &loop) {
345 // Both callbacks should be called before the loop terminates.
346 loop.AddPendingCallback(callback: make_callback());
347 loop.AddPendingCallback(callback: make_callback());
348 loop.RequestTermination();
349 },
350 error);
351 ASSERT_TRUE(error.Success());
352 ASSERT_TRUE(handle);
353 ASSERT_TRUE(loop.Run().Success());
354 ASSERT_EQ(2u, callback_count);
355}
356
357TEST_F(MainLoopTest, PendingCallbackCalledOnlyOnce) {
358 char X = 'X';
359 size_t len = sizeof(X);
360 ASSERT_TRUE(socketpair[0]->Write(&X, len).Success());
361
362 MainLoop loop;
363 Status error;
364 auto handle = loop.RegisterReadObject(
365 object_sp: socketpair[1],
366 callback: [&](MainLoopBase &loop) {
367 // Add one pending callback on the first iteration.
368 if (callback_count == 0) {
369 loop.AddPendingCallback(
370 callback: [&](MainLoopBase &loop) { callback_count++; });
371 }
372 // Terminate the loop on second iteration.
373 if (callback_count++ >= 1)
374 loop.RequestTermination();
375 },
376 error);
377 ASSERT_TRUE(error.Success());
378 ASSERT_TRUE(handle);
379 ASSERT_TRUE(loop.Run().Success());
380 // 2 iterations of read callback + 1 call of pending callback.
381 ASSERT_EQ(3u, callback_count);
382}
383
384TEST_F(MainLoopTest, PendingCallbackTrigger) {
385 MainLoop loop;
386 std::promise<void> add_callback2;
387 bool callback1_called = false;
388 loop.AddPendingCallback(callback: [&](MainLoopBase &loop) {
389 callback1_called = true;
390 add_callback2.set_value();
391 });
392 Status error;
393 ASSERT_THAT_ERROR(error.ToError(), llvm::Succeeded());
394 bool callback2_called = false;
395 std::thread callback2_adder([&]() {
396 add_callback2.get_future().get();
397 loop.AddPendingCallback(callback: [&](MainLoopBase &loop) {
398 callback2_called = true;
399 loop.RequestTermination();
400 });
401 });
402 ASSERT_THAT_ERROR(loop.Run().ToError(), llvm::Succeeded());
403 callback2_adder.join();
404 ASSERT_TRUE(callback1_called);
405 ASSERT_TRUE(callback2_called);
406}
407
408TEST_F(MainLoopTest, ManyPendingCallbacks) {
409 MainLoop loop;
410 Status error;
411 // Try to fill up the pipe buffer and make sure bad things don't happen. This
412 // is a regression test for the case where writing to the interrupt pipe
413 // caused a deadlock when the pipe filled up (either because the main loop was
414 // not running, because it was slow, or because it was busy/blocked doing
415 // something else).
416 for (int i = 0; i < 65536; ++i)
417 loop.AddPendingCallback(
418 callback: [&](MainLoopBase &loop) { loop.RequestTermination(); });
419 ASSERT_TRUE(loop.Run().Success());
420}
421
422TEST_F(MainLoopTest, CallbackWithTimeout) {
423 MainLoop loop;
424 loop.AddCallback(callback: [](MainLoopBase &loop) { loop.RequestTermination(); },
425 delay: std::chrono::seconds(2));
426 auto start = std::chrono::steady_clock::now();
427 ASSERT_THAT_ERROR(loop.Run().takeError(), llvm::Succeeded());
428 EXPECT_GE(std::chrono::steady_clock::now() - start, std::chrono::seconds(2));
429}
430
431TEST_F(MainLoopTest, TimedCallbacksRunInOrder) {
432 MainLoop loop;
433 auto start = std::chrono::steady_clock::now();
434 std::chrono::milliseconds epsilon(10);
435 std::vector<int> order;
436 auto add_cb = [&](int id) {
437 loop.AddCallback(callback: [&order, id](MainLoopBase &) { order.push_back(x: id); },
438 point: start + id * epsilon);
439 };
440 add_cb(3);
441 add_cb(2);
442 add_cb(4);
443 add_cb(1);
444 loop.AddCallback(callback: [](MainLoopBase &loop) { loop.RequestTermination(); },
445 point: start + 5 * epsilon);
446 ASSERT_THAT_ERROR(loop.Run().takeError(), llvm::Succeeded());
447 EXPECT_GE(std::chrono::steady_clock::now() - start, 5 * epsilon);
448 ASSERT_THAT(order, testing::ElementsAre(1, 2, 3, 4));
449}
450
451TEST_F(MainLoopTest, TimedCallbackShortensSleep) {
452 MainLoop loop;
453 auto start = std::chrono::steady_clock::now();
454 bool long_callback_called = false;
455 loop.AddCallback(
456 callback: [&](MainLoopBase &loop) {
457 long_callback_called = true;
458 loop.RequestTermination();
459 },
460 delay: std::chrono::seconds(30));
461 std::future<Status> async_run =
462 std::async(policy: std::launch::async, fn: &MainLoop::Run, args: std::ref(t&: loop));
463 std::this_thread::sleep_for(rtime: std::chrono::milliseconds(100));
464 bool short_callback_called = false;
465 loop.AddCallback(
466 callback: [&](MainLoopBase &loop) {
467 short_callback_called = true;
468 loop.RequestTermination();
469 },
470 delay: std::chrono::seconds(1));
471 ASSERT_THAT_ERROR(async_run.get().takeError(), llvm::Succeeded());
472 EXPECT_LT(std::chrono::steady_clock::now() - start, std::chrono::seconds(10));
473 EXPECT_TRUE(short_callback_called);
474 EXPECT_FALSE(long_callback_called);
475}
476
477#ifdef LLVM_ON_UNIX
478TEST_F(MainLoopTest, DetectsEOF) {
479
480 PseudoTerminal term;
481 ASSERT_THAT_ERROR(term.OpenFirstAvailablePrimary(O_RDWR), llvm::Succeeded());
482 ASSERT_THAT_ERROR(term.OpenSecondary(O_RDWR | O_NOCTTY), llvm::Succeeded());
483 auto conn = std::make_unique<ConnectionFileDescriptor>(
484 args: term.ReleasePrimaryFileDescriptor(), args: true);
485
486 Status error;
487 MainLoop loop;
488 auto handle =
489 loop.RegisterReadObject(object_sp: conn->GetReadObject(), callback: make_callback(), error);
490 ASSERT_TRUE(error.Success());
491 term.CloseSecondaryFileDescriptor();
492
493 ASSERT_TRUE(loop.Run().Success());
494 ASSERT_EQ(1u, callback_count);
495}
496
497TEST_F(MainLoopTest, Signal) {
498 MainLoop loop;
499 Status error;
500
501 auto handle = loop.RegisterSignal(SIGUSR1, callback: make_callback(), error);
502 ASSERT_TRUE(error.Success());
503 kill(pid: getpid(), SIGUSR1);
504 ASSERT_TRUE(loop.Run().Success());
505 ASSERT_EQ(1u, callback_count);
506}
507
508TEST_F(MainLoopTest, SignalOnOtherThread) {
509 MainLoop loop;
510 Status error;
511
512 auto handle = loop.RegisterSignal(SIGUSR1, callback: make_callback(), error);
513 ASSERT_TRUE(error.Success());
514 std::thread([] { pthread_kill(threadid: pthread_self(), SIGUSR1); }).join();
515 ASSERT_TRUE(loop.Run().Success());
516 ASSERT_EQ(1u, callback_count);
517}
518
519// Test that a signal which is not monitored by the MainLoop does not
520// cause a premature exit.
521TEST_F(MainLoopTest, UnmonitoredSignal) {
522 MainLoop loop;
523 Status error;
524 struct sigaction sa;
525 sa.sa_sigaction = [](int, siginfo_t *, void *) {};
526 sa.sa_flags = SA_SIGINFO; // important: no SA_RESTART
527 sigemptyset(set: &sa.sa_mask);
528 ASSERT_EQ(0, sigaction(SIGUSR2, &sa, nullptr));
529
530 auto handle = loop.RegisterSignal(SIGUSR1, callback: make_callback(), error);
531 ASSERT_TRUE(error.Success());
532 kill(pid: getpid(), SIGUSR2);
533 kill(pid: getpid(), SIGUSR1);
534 ASSERT_TRUE(loop.Run().Success());
535 ASSERT_EQ(1u, callback_count);
536}
537
538// Test that two callbacks can be registered for the same signal
539// and unregistered independently.
540TEST_F(MainLoopTest, TwoSignalCallbacks) {
541 MainLoop loop;
542 Status error;
543 unsigned callback2_count = 0;
544 unsigned callback3_count = 0;
545
546 auto handle = loop.RegisterSignal(SIGUSR1, callback: make_callback(), error);
547 ASSERT_TRUE(error.Success());
548
549 {
550 // Run a single iteration with two callbacks enabled.
551 auto handle2 = loop.RegisterSignal(
552 SIGUSR1, callback: [&](MainLoopBase &loop) { ++callback2_count; }, error);
553 ASSERT_TRUE(error.Success());
554
555 kill(pid: getpid(), SIGUSR1);
556 ASSERT_TRUE(loop.Run().Success());
557 ASSERT_EQ(1u, callback_count);
558 ASSERT_EQ(1u, callback2_count);
559 ASSERT_EQ(0u, callback3_count);
560 }
561
562 {
563 // Make sure that remove + add new works.
564 auto handle3 = loop.RegisterSignal(
565 SIGUSR1, callback: [&](MainLoopBase &loop) { ++callback3_count; }, error);
566 ASSERT_TRUE(error.Success());
567
568 kill(pid: getpid(), SIGUSR1);
569 ASSERT_TRUE(loop.Run().Success());
570 ASSERT_EQ(2u, callback_count);
571 ASSERT_EQ(1u, callback2_count);
572 ASSERT_EQ(1u, callback3_count);
573 }
574
575 // Both extra callbacks should be unregistered now.
576 kill(pid: getpid(), SIGUSR1);
577 ASSERT_TRUE(loop.Run().Success());
578 ASSERT_EQ(3u, callback_count);
579 ASSERT_EQ(1u, callback2_count);
580 ASSERT_EQ(1u, callback3_count);
581}
582#endif
583

source code of lldb/unittests/Host/MainLoopTest.cpp