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