Skip to content

Commit c25c6c3

Browse files
authored
[lldb] Unify/improve MainLoop signal handling (#115197)
Change the signal handler to use a pipe to notify about incoming signals. This has two benefits: - the signal no longer has to happen on the MainLoop thread. With the previous implementation, this had to be the case as that was the only way to ensure that ppoll gets interrupted correctly. In a multithreaded process, this would mean that all other threads have to have the signal blocked at all times. - we don't need the android-specific workaround, which was necessary due to the syscall being implemented in a non-atomic way When the MainLoop class was first implemented, we did not have the interrupt self-pipe, so syscall interruption was the most straight-forward implementation. Over time, the class gained new abilities (the pipe being one of them), so we can now piggy-back on those. This patch also changes the kevent-based implementation to use the pipe for signal notification as well. The motivation there is slightly different: - it makes the implementations more uniform - it makes sure we handle all kinds of signals, like we do with the linux version (EVFILT_SIGNAL only catches process-directed signals)
1 parent 030179c commit c25c6c3

File tree

3 files changed

+81
-119
lines changed

3 files changed

+81
-119
lines changed

lldb/include/lldb/Host/posix/MainLoopPosix.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class MainLoopPosix : public MainLoopBase {
5959
private:
6060
void ProcessReadObject(IOObject::WaitableHandle handle);
6161
void ProcessSignal(int signo);
62+
void ProcessSignals();
6263

6364
class SignalHandle {
6465
public:

lldb/source/Host/posix/MainLoopPosix.cpp

Lines changed: 69 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,54 @@
1717
#include <cerrno>
1818
#include <csignal>
1919
#include <ctime>
20+
#include <fcntl.h>
2021
#include <vector>
2122

2223
// Multiplexing is implemented using kqueue on systems that support it (BSD
23-
// variants including OSX). On linux we use ppoll, while android uses pselect
24-
// (ppoll is present but not implemented properly). On windows we use WSApoll
25-
// (which does not support signals).
24+
// variants including OSX). On linux we use ppoll.
2625

2726
#if HAVE_SYS_EVENT_H
2827
#include <sys/event.h>
29-
#elif defined(__ANDROID__)
30-
#include <sys/syscall.h>
3128
#else
3229
#include <poll.h>
3330
#endif
3431

3532
using namespace lldb;
3633
using namespace lldb_private;
3734

38-
static sig_atomic_t g_signal_flags[NSIG];
35+
namespace {
36+
struct GlobalSignalInfo {
37+
sig_atomic_t pipe_fd = -1;
38+
static_assert(sizeof(sig_atomic_t) >= sizeof(int),
39+
"Type too small for a file descriptor");
40+
sig_atomic_t flag = 0;
41+
};
42+
} // namespace
43+
static GlobalSignalInfo g_signal_info[NSIG];
3944

4045
static void SignalHandler(int signo, siginfo_t *info, void *) {
4146
assert(signo < NSIG);
42-
g_signal_flags[signo] = 1;
47+
48+
// Set the flag before writing to the pipe!
49+
g_signal_info[signo].flag = 1;
50+
51+
int fd = g_signal_info[signo].pipe_fd;
52+
if (fd < 0) {
53+
// This can happen with the following (unlikely) sequence of events:
54+
// 1. Thread 1 gets a signal, starts running the signal handler
55+
// 2. Thread 2 unregisters the signal handler, setting pipe_fd to -1
56+
// 3. Signal handler on thread 1 reads -1 out of pipe_fd
57+
// In this case, we can just ignore the signal because we're no longer
58+
// interested in it.
59+
return;
60+
}
61+
62+
// Write a(ny) character to the pipe to wake up from the poll syscall.
63+
char c = '.';
64+
ssize_t bytes_written = llvm::sys::RetryAfterSignal(-1, ::write, fd, &c, 1);
65+
// We can safely ignore EAGAIN (pipe full), as that means poll will definitely
66+
// return.
67+
assert(bytes_written == 1 || (bytes_written == -1 && errno == EAGAIN));
4368
}
4469

4570
class MainLoopPosix::RunImpl {
@@ -48,7 +73,7 @@ class MainLoopPosix::RunImpl {
4873
~RunImpl() = default;
4974

5075
Status Poll();
51-
void ProcessEvents();
76+
void ProcessReadEvents();
5277

5378
private:
5479
MainLoopPosix &loop;
@@ -58,15 +83,9 @@ class MainLoopPosix::RunImpl {
5883
struct kevent out_events[4];
5984
int num_events = -1;
6085

61-
#else
62-
#ifdef __ANDROID__
63-
fd_set read_fd_set;
6486
#else
6587
std::vector<struct pollfd> read_fds;
6688
#endif
67-
68-
sigset_t get_sigmask();
69-
#endif
7089
};
7190

7291
#if HAVE_SYS_EVENT_H
@@ -94,7 +113,7 @@ Status MainLoopPosix::RunImpl::Poll() {
94113
return Status();
95114
}
96115

97-
void MainLoopPosix::RunImpl::ProcessEvents() {
116+
void MainLoopPosix::RunImpl::ProcessReadEvents() {
98117
assert(num_events >= 0);
99118
for (int i = 0; i < num_events; ++i) {
100119
if (loop.m_terminate_request)
@@ -103,74 +122,19 @@ void MainLoopPosix::RunImpl::ProcessEvents() {
103122
case EVFILT_READ:
104123
loop.ProcessReadObject(out_events[i].ident);
105124
break;
106-
case EVFILT_SIGNAL:
107-
loop.ProcessSignal(out_events[i].ident);
108-
break;
109125
default:
110126
llvm_unreachable("Unknown event");
111127
}
112128
}
113129
}
114130
#else
115131
MainLoopPosix::RunImpl::RunImpl(MainLoopPosix &loop) : loop(loop) {
116-
#ifndef __ANDROID__
117132
read_fds.reserve(loop.m_read_fds.size());
118-
#endif
119133
}
120134

121-
sigset_t MainLoopPosix::RunImpl::get_sigmask() {
122-
sigset_t sigmask;
123-
int ret = pthread_sigmask(SIG_SETMASK, nullptr, &sigmask);
124-
assert(ret == 0);
125-
UNUSED_IF_ASSERT_DISABLED(ret);
126-
127-
for (const auto &sig : loop.m_signals)
128-
sigdelset(&sigmask, sig.first);
129-
return sigmask;
130-
}
131-
132-
#ifdef __ANDROID__
133-
Status MainLoopPosix::RunImpl::Poll() {
134-
// ppoll(2) is not supported on older all android versions. Also, older
135-
// versions android (API <= 19) implemented pselect in a non-atomic way, as a
136-
// combination of pthread_sigmask and select. This is not sufficient for us,
137-
// as we rely on the atomicity to correctly implement signal polling, so we
138-
// call the underlying syscall ourselves.
139-
140-
FD_ZERO(&read_fd_set);
141-
int nfds = 0;
142-
for (const auto &fd : loop.m_read_fds) {
143-
FD_SET(fd.first, &read_fd_set);
144-
nfds = std::max(nfds, fd.first + 1);
145-
}
146-
147-
union {
148-
sigset_t set;
149-
uint64_t pad;
150-
} kernel_sigset;
151-
memset(&kernel_sigset, 0, sizeof(kernel_sigset));
152-
kernel_sigset.set = get_sigmask();
153-
154-
struct {
155-
void *sigset_ptr;
156-
size_t sigset_len;
157-
} extra_data = {&kernel_sigset, sizeof(kernel_sigset)};
158-
if (syscall(__NR_pselect6, nfds, &read_fd_set, nullptr, nullptr, nullptr,
159-
&extra_data) == -1) {
160-
if (errno != EINTR)
161-
return Status(errno, eErrorTypePOSIX);
162-
else
163-
FD_ZERO(&read_fd_set);
164-
}
165-
166-
return Status();
167-
}
168-
#else
169135
Status MainLoopPosix::RunImpl::Poll() {
170136
read_fds.clear();
171137

172-
sigset_t sigmask = get_sigmask();
173-
174138
for (const auto &fd : loop.m_read_fds) {
175139
struct pollfd pfd;
176140
pfd.fd = fd.first;
@@ -179,55 +143,39 @@ Status MainLoopPosix::RunImpl::Poll() {
179143
read_fds.push_back(pfd);
180144
}
181145

182-
if (ppoll(read_fds.data(), read_fds.size(), nullptr, &sigmask) == -1 &&
146+
if (ppoll(read_fds.data(), read_fds.size(),
147+
/*timeout=*/nullptr,
148+
/*sigmask=*/nullptr) == -1 &&
183149
errno != EINTR)
184150
return Status(errno, eErrorTypePOSIX);
185151

186152
return Status();
187153
}
188-
#endif
189154

190-
void MainLoopPosix::RunImpl::ProcessEvents() {
191-
#ifdef __ANDROID__
192-
// Collect first all readable file descriptors into a separate vector and
193-
// then iterate over it to invoke callbacks. Iterating directly over
194-
// loop.m_read_fds is not possible because the callbacks can modify the
195-
// container which could invalidate the iterator.
196-
std::vector<IOObject::WaitableHandle> fds;
197-
for (const auto &fd : loop.m_read_fds)
198-
if (FD_ISSET(fd.first, &read_fd_set))
199-
fds.push_back(fd.first);
200-
201-
for (const auto &handle : fds) {
202-
#else
155+
void MainLoopPosix::RunImpl::ProcessReadEvents() {
203156
for (const auto &fd : read_fds) {
204157
if ((fd.revents & (POLLIN | POLLHUP)) == 0)
205158
continue;
206159
IOObject::WaitableHandle handle = fd.fd;
207-
#endif
208160
if (loop.m_terminate_request)
209161
return;
210162

211163
loop.ProcessReadObject(handle);
212164
}
213-
214-
std::vector<int> signals;
215-
for (const auto &entry : loop.m_signals)
216-
if (g_signal_flags[entry.first] != 0)
217-
signals.push_back(entry.first);
218-
219-
for (const auto &signal : signals) {
220-
if (loop.m_terminate_request)
221-
return;
222-
g_signal_flags[signal] = 0;
223-
loop.ProcessSignal(signal);
224-
}
225165
}
226166
#endif
227167

228168
MainLoopPosix::MainLoopPosix() : m_triggering(false) {
229169
Status error = m_trigger_pipe.CreateNew(/*child_process_inherit=*/false);
230170
assert(error.Success());
171+
172+
// Make the write end of the pipe non-blocking.
173+
int result = fcntl(m_trigger_pipe.GetWriteFileDescriptor(), F_SETFL,
174+
fcntl(m_trigger_pipe.GetWriteFileDescriptor(), F_GETFL) |
175+
O_NONBLOCK);
176+
assert(result == 0);
177+
UNUSED_IF_ASSERT_DISABLED(result);
178+
231179
const int trigger_pipe_fd = m_trigger_pipe.GetReadFileDescriptor();
232180
m_read_fds.insert({trigger_pipe_fd, [trigger_pipe_fd](MainLoopBase &loop) {
233181
char c;
@@ -295,26 +243,17 @@ MainLoopPosix::RegisterSignal(int signo, const Callback &callback,
295243
sigaddset(&new_action.sa_mask, signo);
296244
sigset_t old_set;
297245

298-
g_signal_flags[signo] = 0;
246+
// Set signal info before installing the signal handler!
247+
g_signal_info[signo].pipe_fd = m_trigger_pipe.GetWriteFileDescriptor();
248+
g_signal_info[signo].flag = 0;
299249

300250
// Even if using kqueue, the signal handler will still be invoked, so it's
301251
// important to replace it with our "benign" handler.
302252
int ret = sigaction(signo, &new_action, &info.old_action);
303253
UNUSED_IF_ASSERT_DISABLED(ret);
304254
assert(ret == 0 && "sigaction failed");
305255

306-
#if HAVE_SYS_EVENT_H
307-
struct kevent ev;
308-
EV_SET(&ev, signo, EVFILT_SIGNAL, EV_ADD, 0, 0, 0);
309-
ret = kevent(m_kqueue, &ev, 1, nullptr, 0, nullptr);
310-
assert(ret == 0);
311-
#endif
312-
313-
// If we're using kqueue, the signal needs to be unblocked in order to
314-
// receive it. If using pselect/ppoll, we need to block it, and later unblock
315-
// it as a part of the system call.
316-
ret = pthread_sigmask(HAVE_SYS_EVENT_H ? SIG_UNBLOCK : SIG_BLOCK,
317-
&new_action.sa_mask, &old_set);
256+
ret = pthread_sigmask(SIG_UNBLOCK, &new_action.sa_mask, &old_set);
318257
assert(ret == 0 && "pthread_sigmask failed");
319258
info.was_blocked = sigismember(&old_set, signo);
320259
auto insert_ret = m_signals.insert({signo, info});
@@ -349,14 +288,8 @@ void MainLoopPosix::UnregisterSignal(
349288
assert(ret == 0);
350289
UNUSED_IF_ASSERT_DISABLED(ret);
351290

352-
#if HAVE_SYS_EVENT_H
353-
struct kevent ev;
354-
EV_SET(&ev, signo, EVFILT_SIGNAL, EV_DELETE, 0, 0, 0);
355-
ret = kevent(m_kqueue, &ev, 1, nullptr, 0, nullptr);
356-
assert(ret == 0);
357-
#endif
358-
359291
m_signals.erase(it);
292+
g_signal_info[signo] = {};
360293
}
361294

362295
Status MainLoopPosix::Run() {
@@ -370,7 +303,9 @@ Status MainLoopPosix::Run() {
370303
if (error.Fail())
371304
return error;
372305

373-
impl.ProcessEvents();
306+
impl.ProcessReadEvents();
307+
308+
ProcessSignals();
374309

375310
m_triggering = false;
376311
ProcessPendingCallbacks();
@@ -384,6 +319,21 @@ void MainLoopPosix::ProcessReadObject(IOObject::WaitableHandle handle) {
384319
it->second(*this); // Do the work
385320
}
386321

322+
void MainLoopPosix::ProcessSignals() {
323+
std::vector<int> signals;
324+
for (const auto &entry : m_signals)
325+
if (g_signal_info[entry.first].flag != 0)
326+
signals.push_back(entry.first);
327+
328+
for (const auto &signal : signals) {
329+
if (m_terminate_request)
330+
return;
331+
332+
g_signal_info[signal].flag = 0;
333+
ProcessSignal(signal);
334+
}
335+
}
336+
387337
void MainLoopPosix::ProcessSignal(int signo) {
388338
auto it = m_signals.find(signo);
389339
if (it != m_signals.end()) {

lldb/unittests/Host/MainLoopTest.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,17 @@ TEST_F(MainLoopTest, Signal) {
254254
ASSERT_EQ(1u, callback_count);
255255
}
256256

257+
TEST_F(MainLoopTest, SignalOnOtherThread) {
258+
MainLoop loop;
259+
Status error;
260+
261+
auto handle = loop.RegisterSignal(SIGUSR1, make_callback(), error);
262+
ASSERT_TRUE(error.Success());
263+
std::thread([] { pthread_kill(pthread_self(), SIGUSR1); }).join();
264+
ASSERT_TRUE(loop.Run().Success());
265+
ASSERT_EQ(1u, callback_count);
266+
}
267+
257268
// Test that a signal which is not monitored by the MainLoop does not
258269
// cause a premature exit.
259270
TEST_F(MainLoopTest, UnmonitoredSignal) {

0 commit comments

Comments
 (0)