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/PseudoTerminal.h"
14#include "lldb/Host/common/TCPSocket.h"
15#include "llvm/Testing/Support/Error.h"
16#include "gtest/gtest.h"
17#include <future>
18
19using namespace lldb_private;
20
21namespace {
22class MainLoopTest : public testing::Test {
23public:
24 SubsystemRAII<FileSystem, Socket> subsystems;
25
26 void SetUp() override {
27 bool child_processes_inherit = false;
28 Status error;
29 std::unique_ptr<TCPSocket> listen_socket_up(
30 new TCPSocket(true, child_processes_inherit));
31 ASSERT_TRUE(error.Success());
32 error = listen_socket_up->Listen(name: "localhost:0", backlog: 5);
33 ASSERT_TRUE(error.Success());
34
35 Socket *accept_socket;
36 std::unique_ptr<TCPSocket> connect_socket_up(
37 new TCPSocket(true, child_processes_inherit));
38 error = connect_socket_up->Connect(
39 name: llvm::formatv(Fmt: "localhost:{0}", Vals: listen_socket_up->GetLocalPortNumber())
40 .str());
41 ASSERT_TRUE(error.Success());
42 ASSERT_TRUE(listen_socket_up->Accept(accept_socket).Success());
43
44 callback_count = 0;
45 socketpair[0] = std::move(connect_socket_up);
46 socketpair[1].reset(p: accept_socket);
47 }
48
49 void TearDown() override {
50 socketpair[0].reset();
51 socketpair[1].reset();
52 }
53
54protected:
55 MainLoop::Callback make_callback() {
56 return [&](MainLoopBase &loop) {
57 ++callback_count;
58 loop.RequestTermination();
59 };
60 }
61 std::shared_ptr<Socket> socketpair[2];
62 unsigned callback_count;
63};
64} // namespace
65
66TEST_F(MainLoopTest, ReadObject) {
67 char X = 'X';
68 size_t len = sizeof(X);
69 ASSERT_TRUE(socketpair[0]->Write(&X, len).Success());
70
71 MainLoop loop;
72
73 Status error;
74 auto handle = loop.RegisterReadObject(object_sp: socketpair[1], callback: make_callback(), error);
75 ASSERT_TRUE(error.Success());
76 ASSERT_TRUE(handle);
77 ASSERT_TRUE(loop.Run().Success());
78 ASSERT_EQ(1u, callback_count);
79}
80
81TEST_F(MainLoopTest, TerminatesImmediately) {
82 char X = 'X';
83 size_t len = sizeof(X);
84 ASSERT_TRUE(socketpair[0]->Write(&X, len).Success());
85 ASSERT_TRUE(socketpair[1]->Write(&X, len).Success());
86
87 MainLoop loop;
88 Status error;
89 auto handle0 = loop.RegisterReadObject(object_sp: socketpair[0], callback: make_callback(), error);
90 ASSERT_TRUE(error.Success());
91 auto handle1 = loop.RegisterReadObject(object_sp: socketpair[1], callback: make_callback(), error);
92 ASSERT_TRUE(error.Success());
93
94 ASSERT_TRUE(loop.Run().Success());
95 ASSERT_EQ(1u, callback_count);
96}
97
98TEST_F(MainLoopTest, PendingCallback) {
99 char X = 'X';
100 size_t len = sizeof(X);
101 ASSERT_TRUE(socketpair[0]->Write(&X, len).Success());
102
103 MainLoop loop;
104 Status error;
105 auto handle = loop.RegisterReadObject(
106 object_sp: socketpair[1],
107 callback: [&](MainLoopBase &loop) {
108 // Both callbacks should be called before the loop terminates.
109 loop.AddPendingCallback(callback: make_callback());
110 loop.AddPendingCallback(callback: make_callback());
111 loop.RequestTermination();
112 },
113 error);
114 ASSERT_TRUE(error.Success());
115 ASSERT_TRUE(handle);
116 ASSERT_TRUE(loop.Run().Success());
117 ASSERT_EQ(2u, callback_count);
118}
119
120TEST_F(MainLoopTest, PendingCallbackCalledOnlyOnce) {
121 char X = 'X';
122 size_t len = sizeof(X);
123 ASSERT_TRUE(socketpair[0]->Write(&X, len).Success());
124
125 MainLoop loop;
126 Status error;
127 auto handle = loop.RegisterReadObject(
128 object_sp: socketpair[1],
129 callback: [&](MainLoopBase &loop) {
130 // Add one pending callback on the first iteration.
131 if (callback_count == 0) {
132 loop.AddPendingCallback(callback: [&](MainLoopBase &loop) {
133 callback_count++;
134 });
135 }
136 // Terminate the loop on second iteration.
137 if (callback_count++ >= 1)
138 loop.RequestTermination();
139 },
140 error);
141 ASSERT_TRUE(error.Success());
142 ASSERT_TRUE(handle);
143 ASSERT_TRUE(loop.Run().Success());
144 // 2 iterations of read callback + 1 call of pending callback.
145 ASSERT_EQ(3u, callback_count);
146}
147
148TEST_F(MainLoopTest, PendingCallbackTrigger) {
149 MainLoop loop;
150 std::promise<void> add_callback2;
151 bool callback1_called = false;
152 loop.AddPendingCallback(callback: [&](MainLoopBase &loop) {
153 callback1_called = true;
154 add_callback2.set_value();
155 });
156 Status error;
157 auto socket_handle = loop.RegisterReadObject(
158 object_sp: socketpair[1], callback: [](MainLoopBase &) {}, error);
159 ASSERT_TRUE(socket_handle);
160 ASSERT_THAT_ERROR(error.ToError(), llvm::Succeeded());
161 bool callback2_called = false;
162 std::thread callback2_adder([&]() {
163 add_callback2.get_future().get();
164 loop.AddPendingCallback(callback: [&](MainLoopBase &loop) {
165 callback2_called = true;
166 loop.RequestTermination();
167 });
168 });
169 ASSERT_THAT_ERROR(loop.Run().ToError(), llvm::Succeeded());
170 callback2_adder.join();
171 ASSERT_TRUE(callback1_called);
172 ASSERT_TRUE(callback2_called);
173}
174
175// Regression test for assertion failure if a lot of callbacks end up
176// being queued after loop exits.
177TEST_F(MainLoopTest, PendingCallbackAfterLoopExited) {
178 MainLoop loop;
179 Status error;
180 ASSERT_TRUE(loop.Run().Success());
181 // Try to fill the pipe buffer in.
182 for (int i = 0; i < 65536; ++i)
183 loop.AddPendingCallback(callback: [&](MainLoopBase &loop) {});
184}
185
186#ifdef LLVM_ON_UNIX
187TEST_F(MainLoopTest, DetectsEOF) {
188
189 PseudoTerminal term;
190 ASSERT_THAT_ERROR(term.OpenFirstAvailablePrimary(O_RDWR), llvm::Succeeded());
191 ASSERT_THAT_ERROR(term.OpenSecondary(O_RDWR | O_NOCTTY), llvm::Succeeded());
192 auto conn = std::make_unique<ConnectionFileDescriptor>(
193 args: term.ReleasePrimaryFileDescriptor(), args: true);
194
195 Status error;
196 MainLoop loop;
197 auto handle =
198 loop.RegisterReadObject(object_sp: conn->GetReadObject(), callback: make_callback(), error);
199 ASSERT_TRUE(error.Success());
200 term.CloseSecondaryFileDescriptor();
201
202 ASSERT_TRUE(loop.Run().Success());
203 ASSERT_EQ(1u, callback_count);
204}
205
206TEST_F(MainLoopTest, Signal) {
207 MainLoop loop;
208 Status error;
209
210 auto handle = loop.RegisterSignal(SIGUSR1, callback: make_callback(), error);
211 ASSERT_TRUE(error.Success());
212 kill(pid: getpid(), SIGUSR1);
213 ASSERT_TRUE(loop.Run().Success());
214 ASSERT_EQ(1u, callback_count);
215}
216
217// Test that a signal which is not monitored by the MainLoop does not
218// cause a premature exit.
219TEST_F(MainLoopTest, UnmonitoredSignal) {
220 MainLoop loop;
221 Status error;
222 struct sigaction sa;
223 sa.sa_sigaction = [](int, siginfo_t *, void *) { };
224 sa.sa_flags = SA_SIGINFO; // important: no SA_RESTART
225 sigemptyset(set: &sa.sa_mask);
226 ASSERT_EQ(0, sigaction(SIGUSR2, &sa, nullptr));
227
228 auto handle = loop.RegisterSignal(SIGUSR1, callback: make_callback(), error);
229 ASSERT_TRUE(error.Success());
230 kill(pid: getpid(), SIGUSR2);
231 kill(pid: getpid(), SIGUSR1);
232 ASSERT_TRUE(loop.Run().Success());
233 ASSERT_EQ(1u, callback_count);
234}
235
236// Test that two callbacks can be registered for the same signal
237// and unregistered independently.
238TEST_F(MainLoopTest, TwoSignalCallbacks) {
239 MainLoop loop;
240 Status error;
241 unsigned callback2_count = 0;
242 unsigned callback3_count = 0;
243
244 auto handle = loop.RegisterSignal(SIGUSR1, callback: make_callback(), error);
245 ASSERT_TRUE(error.Success());
246
247 {
248 // Run a single iteration with two callbacks enabled.
249 auto handle2 = loop.RegisterSignal(
250 SIGUSR1, callback: [&](MainLoopBase &loop) { ++callback2_count; }, error);
251 ASSERT_TRUE(error.Success());
252
253 kill(pid: getpid(), SIGUSR1);
254 ASSERT_TRUE(loop.Run().Success());
255 ASSERT_EQ(1u, callback_count);
256 ASSERT_EQ(1u, callback2_count);
257 ASSERT_EQ(0u, callback3_count);
258 }
259
260 {
261 // Make sure that remove + add new works.
262 auto handle3 = loop.RegisterSignal(
263 SIGUSR1, callback: [&](MainLoopBase &loop) { ++callback3_count; }, error);
264 ASSERT_TRUE(error.Success());
265
266 kill(pid: getpid(), SIGUSR1);
267 ASSERT_TRUE(loop.Run().Success());
268 ASSERT_EQ(2u, callback_count);
269 ASSERT_EQ(1u, callback2_count);
270 ASSERT_EQ(1u, callback3_count);
271 }
272
273 // Both extra callbacks should be unregistered now.
274 kill(pid: getpid(), SIGUSR1);
275 ASSERT_TRUE(loop.Run().Success());
276 ASSERT_EQ(3u, callback_count);
277 ASSERT_EQ(1u, callback2_count);
278 ASSERT_EQ(1u, callback3_count);
279}
280#endif
281

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