Skip to content

Commit 2627a1f

Browse files
committed
[lldb] Unify/improve MainLoop signal handling
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 5dc8d61 commit 2627a1f

File tree

3 files changed

+75
-119
lines changed

3 files changed

+75
-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: 63 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,53 @@
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+
char c = '.';
52+
int fd = g_signal_info[signo].pipe_fd;
53+
if (fd < 0) {
54+
// This can happen with the following (unlikely) sequence of events:
55+
// 1. Thread 1 gets a signal, starts running the signal handler
56+
// 2. Thread 2 unregisters the signal handler, setting pipe_fd to -1
57+
// 3. Signal handler on thread 1 reads -1 out of pipe_fd
58+
// In this case, we can just ignore the signal because we're no longer
59+
// interested in it.
60+
return;
61+
}
62+
63+
ssize_t bytes_written = llvm::sys::RetryAfterSignal(-1, ::write, fd, &c, 1);
64+
// We're only using the pipe to wake up the reader, so we can safely ignore
65+
// EAGAIN (pipe full)
66+
assert(bytes_written == 1 || (bytes_written == -1 && errno == EAGAIN));
4367
}
4468

4569
class MainLoopPosix::RunImpl {
@@ -48,7 +72,7 @@ class MainLoopPosix::RunImpl {
4872
~RunImpl() = default;
4973

5074
Status Poll();
51-
void ProcessEvents();
75+
void ProcessReadEvents();
5276

5377
private:
5478
MainLoopPosix &loop;
@@ -58,15 +82,9 @@ class MainLoopPosix::RunImpl {
5882
struct kevent out_events[4];
5983
int num_events = -1;
6084

61-
#else
62-
#ifdef __ANDROID__
63-
fd_set read_fd_set;
6485
#else
6586
std::vector<struct pollfd> read_fds;
6687
#endif
67-
68-
sigset_t get_sigmask();
69-
#endif
7088
};
7189

7290
#if HAVE_SYS_EVENT_H
@@ -94,7 +112,7 @@ Status MainLoopPosix::RunImpl::Poll() {
94112
return Status();
95113
}
96114

97-
void MainLoopPosix::RunImpl::ProcessEvents() {
115+
void MainLoopPosix::RunImpl::ProcessReadEvents() {
98116
assert(num_events >= 0);
99117
for (int i = 0; i < num_events; ++i) {
100118
if (loop.m_terminate_request)
@@ -103,74 +121,19 @@ void MainLoopPosix::RunImpl::ProcessEvents() {
103121
case EVFILT_READ:
104122
loop.ProcessReadObject(out_events[i].ident);
105123
break;
106-
case EVFILT_SIGNAL:
107-
loop.ProcessSignal(out_events[i].ident);
108-
break;
109124
default:
110125
llvm_unreachable("Unknown event");
111126
}
112127
}
113128
}
114129
#else
115130
MainLoopPosix::RunImpl::RunImpl(MainLoopPosix &loop) : loop(loop) {
116-
#ifndef __ANDROID__
117131
read_fds.reserve(loop.m_read_fds.size());
118-
#endif
119132
}
120133

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
169134
Status MainLoopPosix::RunImpl::Poll() {
170135
read_fds.clear();
171136

172-
sigset_t sigmask = get_sigmask();
173-
174137
for (const auto &fd : loop.m_read_fds) {
175138
struct pollfd pfd;
176139
pfd.fd = fd.first;
@@ -179,55 +142,34 @@ Status MainLoopPosix::RunImpl::Poll() {
179142
read_fds.push_back(pfd);
180143
}
181144

182-
if (ppoll(read_fds.data(), read_fds.size(), nullptr, &sigmask) == -1 &&
145+
if (ppoll(read_fds.data(), read_fds.size(),
146+
/*timeout=*/nullptr,
147+
/*sigmask=*/nullptr) == -1 &&
183148
errno != EINTR)
184149
return Status(errno, eErrorTypePOSIX);
185150

186151
return Status();
187152
}
188-
#endif
189153

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
154+
void MainLoopPosix::RunImpl::ProcessReadEvents() {
203155
for (const auto &fd : read_fds) {
204156
if ((fd.revents & (POLLIN | POLLHUP)) == 0)
205157
continue;
206158
IOObject::WaitableHandle handle = fd.fd;
207-
#endif
208159
if (loop.m_terminate_request)
209160
return;
210161

211162
loop.ProcessReadObject(handle);
212163
}
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-
}
225164
}
226165
#endif
227166

228167
MainLoopPosix::MainLoopPosix() : m_triggering(false) {
229168
Status error = m_trigger_pipe.CreateNew(/*child_process_inherit=*/false);
230169
assert(error.Success());
170+
assert(fcntl(m_trigger_pipe.GetWriteFileDescriptor(), F_SETFL,
171+
O_NONBLOCK | fcntl(m_trigger_pipe.GetWriteFileDescriptor(),
172+
F_GETFL)) == 0);
231173
const int trigger_pipe_fd = m_trigger_pipe.GetReadFileDescriptor();
232174
m_read_fds.insert({trigger_pipe_fd, [trigger_pipe_fd](MainLoopBase &loop) {
233175
char c;
@@ -295,26 +237,17 @@ MainLoopPosix::RegisterSignal(int signo, const Callback &callback,
295237
sigaddset(&new_action.sa_mask, signo);
296238
sigset_t old_set;
297239

298-
g_signal_flags[signo] = 0;
240+
// Set signal info before installing the signal handler!
241+
g_signal_info[signo].pipe_fd = m_trigger_pipe.GetWriteFileDescriptor();
242+
g_signal_info[signo].flag = 0;
299243

300244
// Even if using kqueue, the signal handler will still be invoked, so it's
301245
// important to replace it with our "benign" handler.
302246
int ret = sigaction(signo, &new_action, &info.old_action);
303247
UNUSED_IF_ASSERT_DISABLED(ret);
304248
assert(ret == 0 && "sigaction failed");
305249

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);
250+
ret = pthread_sigmask(SIG_UNBLOCK, &new_action.sa_mask, &old_set);
318251
assert(ret == 0 && "pthread_sigmask failed");
319252
info.was_blocked = sigismember(&old_set, signo);
320253
auto insert_ret = m_signals.insert({signo, info});
@@ -349,14 +282,8 @@ void MainLoopPosix::UnregisterSignal(
349282
assert(ret == 0);
350283
UNUSED_IF_ASSERT_DISABLED(ret);
351284

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-
359285
m_signals.erase(it);
286+
g_signal_info[signo] = {};
360287
}
361288

362289
Status MainLoopPosix::Run() {
@@ -370,7 +297,9 @@ Status MainLoopPosix::Run() {
370297
if (error.Fail())
371298
return error;
372299

373-
impl.ProcessEvents();
300+
impl.ProcessReadEvents();
301+
302+
ProcessSignals();
374303

375304
m_triggering = false;
376305
ProcessPendingCallbacks();
@@ -384,6 +313,21 @@ void MainLoopPosix::ProcessReadObject(IOObject::WaitableHandle handle) {
384313
it->second(*this); // Do the work
385314
}
386315

316+
void MainLoopPosix::ProcessSignals() {
317+
std::vector<int> signals;
318+
for (const auto &entry : m_signals)
319+
if (g_signal_info[entry.first].flag != 0)
320+
signals.push_back(entry.first);
321+
322+
for (const auto &signal : signals) {
323+
if (m_terminate_request)
324+
return;
325+
326+
g_signal_info[signal].flag = 0;
327+
ProcessSignal(signal);
328+
}
329+
}
330+
387331
void MainLoopPosix::ProcessSignal(int signo) {
388332
auto it = m_signals.find(signo);
389333
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)