Skip to content

Commit

Permalink
MainLoop: Add unit tests
Browse files Browse the repository at this point in the history
Summary:
This adds a couple of unit tests to the MainLoop class. To get the
kqueue based version of the signal handling passing, I needed to
modify the implementation a bit to make the queue object persistent.
Otherwise, only the signals which are send during the Run call would get
processed, which did not match the ppoll behaviour.

I also took the opportunity to remove the ForEach template functions and
replace them with something more reasonable.

Reviewers: beanz, eugene

Subscribers: lldb-commits, mgorny

Differential Revision: https://reviews.llvm.org/D32753

llvm-svn: 302133
  • Loading branch information
labath committed May 4, 2017
1 parent 1859463 commit 4f1f641
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 105 deletions.
7 changes: 7 additions & 0 deletions lldb/include/lldb/Host/MainLoop.h
Expand Up @@ -42,6 +42,7 @@ class MainLoop : public MainLoopBase {
public:
typedef std::unique_ptr<SignalHandle> SignalHandleUP;

MainLoop();
~MainLoop() override;

ReadHandleUP RegisterReadObject(const lldb::IOObjectSP &object_sp,
Expand Down Expand Up @@ -71,6 +72,9 @@ class MainLoop : public MainLoopBase {
void UnregisterSignal(int signo);

private:
void ProcessReadObject(IOObject::WaitableHandle handle);
void ProcessSignal(int signo);

class SignalHandle {
public:
~SignalHandle() { m_mainloop.UnregisterSignal(m_signo); }
Expand All @@ -97,6 +101,9 @@ class MainLoop : public MainLoopBase {

llvm::DenseMap<IOObject::WaitableHandle, Callback> m_read_fds;
llvm::DenseMap<int, SignalInfo> m_signals;
#if HAVE_SYS_EVENT_H
int m_kqueue;
#endif
bool m_terminate_request : 1;
};

Expand Down
206 changes: 101 additions & 105 deletions lldb/source/Host/common/MainLoop.cpp
Expand Up @@ -18,6 +18,11 @@
#include <vector>
#include <time.h>

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

#if HAVE_SYS_EVENT_H
#include <sys/event.h>
#elif defined(LLVM_ON_WIN32)
Expand Down Expand Up @@ -65,92 +70,72 @@ static void SignalHandler(int signo, siginfo_t *info, void *) {

class MainLoop::RunImpl {
public:
// TODO: Use llvm::Expected<T>
static std::unique_ptr<RunImpl> Create(MainLoop &loop, Error &error);
~RunImpl();
RunImpl(MainLoop &loop);
~RunImpl() = default;

Error Poll();

template <typename F> void ForEachReadFD(F &&f);
template <typename F> void ForEachSignal(F &&f);
void ProcessEvents();

private:
MainLoop &loop;

#if HAVE_SYS_EVENT_H
int queue_id;
std::vector<struct kevent> in_events;
struct kevent out_events[4];
int num_events = -1;

RunImpl(MainLoop &loop, int queue_id) : loop(loop), queue_id(queue_id) {
in_events.reserve(loop.m_read_fds.size() + loop.m_signals.size());
}
#else
std::vector<int> signals;
#ifdef FORCE_PSELECT
fd_set read_fd_set;
#else
std::vector<struct pollfd> read_fds;
#endif

RunImpl(MainLoop &loop) : loop(loop) {
signals.reserve(loop.m_signals.size());
}

sigset_t get_sigmask();
#endif
};

#if HAVE_SYS_EVENT_H
MainLoop::RunImpl::~RunImpl() {
int r = close(queue_id);
assert(r == 0);
(void)r;
}
std::unique_ptr<MainLoop::RunImpl> MainLoop::RunImpl::Create(MainLoop &loop, Error &error)
{
error.Clear();
int queue_id = kqueue();
if(queue_id < 0) {
error = Error(errno, eErrorTypePOSIX);
return nullptr;
}
return std::unique_ptr<RunImpl>(new RunImpl(loop, queue_id));
MainLoop::RunImpl::RunImpl(MainLoop &loop) : loop(loop) {
in_events.reserve(loop.m_read_fds.size());
}

Error MainLoop::RunImpl::Poll() {
in_events.resize(loop.m_read_fds.size() + loop.m_signals.size());
in_events.resize(loop.m_read_fds.size());
unsigned i = 0;
for (auto &fd : loop.m_read_fds)
EV_SET(&in_events[i++], fd.first, EVFILT_READ, EV_ADD, 0, 0, 0);

for (const auto &sig : loop.m_signals)
EV_SET(&in_events[i++], sig.first, EVFILT_SIGNAL, EV_ADD, 0, 0, 0);

num_events = kevent(queue_id, in_events.data(), in_events.size(), out_events,
llvm::array_lengthof(out_events), nullptr);
num_events = kevent(loop.m_kqueue, in_events.data(), in_events.size(),
out_events, llvm::array_lengthof(out_events), nullptr);

if (num_events < 0)
return Error("kevent() failed with error %d\n", num_events);
return Error();
}

template <typename F> void MainLoop::RunImpl::ForEachReadFD(F &&f) {
void MainLoop::RunImpl::ProcessEvents() {
assert(num_events >= 0);
for (int i = 0; i < num_events; ++i) {
f(out_events[i].ident);
if (loop.m_terminate_request)
return;
switch (out_events[i].filter) {
case EVFILT_READ:
loop.ProcessReadObject(out_events[i].ident);
break;
case EVFILT_SIGNAL:
loop.ProcessSignal(out_events[i].ident);
break;
default:
llvm_unreachable("Unknown event");
}
}
}
template <typename F> void MainLoop::RunImpl::ForEachSignal(F && f) {}
#else
MainLoop::RunImpl::~RunImpl() {}
std::unique_ptr<MainLoop::RunImpl> MainLoop::RunImpl::Create(MainLoop &loop, Error &error)
{
error.Clear();
return std::unique_ptr<RunImpl>(new RunImpl(loop));
MainLoop::RunImpl::RunImpl(MainLoop &loop) : loop(loop) {
#ifndef FORCE_PSELECT
read_fds.reserve(loop.m_read_fds.size());
#endif
}

sigset_t MainLoop::RunImpl::get_sigmask() {
Expand All @@ -162,18 +147,14 @@ sigset_t MainLoop::RunImpl::get_sigmask() {
assert(ret == 0);
(void) ret;

for (const auto &sig : loop.m_signals) {
signals.push_back(sig.first);
for (const auto &sig : loop.m_signals)
sigdelset(&sigmask, sig.first);
}
return sigmask;
#endif
}

#ifdef FORCE_PSELECT
Error MainLoop::RunImpl::Poll() {
signals.clear();

FD_ZERO(&read_fd_set);
int nfds = 0;
for (const auto &fd : loop.m_read_fds) {
Expand All @@ -188,20 +169,8 @@ Error MainLoop::RunImpl::Poll() {

return Error();
}

template <typename F> void MainLoop::RunImpl::ForEachReadFD(F &&f) {
for (const auto &fd : loop.m_read_fds) {
if(!FD_ISSET(fd.first, &read_fd_set))
continue;

f(fd.first);
if (loop.m_terminate_request)
return;
}
}
#else
Error MainLoop::RunImpl::Poll() {
signals.clear();
read_fds.clear();

sigset_t sigmask = get_sigmask();
Expand All @@ -220,33 +189,47 @@ Error MainLoop::RunImpl::Poll() {

return Error();
}
#endif

template <typename F> void MainLoop::RunImpl::ForEachReadFD(F &&f) {
void MainLoop::RunImpl::ProcessEvents() {
#ifdef FORCE_PSELECT
for (const auto &fd : loop.m_read_fds) {
if (!FD_ISSET(fd.first, &read_fd_set))
continue;
IOObject::WaitableHandle handle = fd.first;
#else
for (const auto &fd : read_fds) {
if ((fd.revents & POLLIN) == 0)
continue;

f(fd.fd);
IOObject::WaitableHandle handle = fd.fd;
#endif
if (loop.m_terminate_request)
return;
}
}
#endif

template <typename F> void MainLoop::RunImpl::ForEachSignal(F &&f) {
for (int sig : signals) {
if (g_signal_flags[sig] == 0)
continue; // No signal
g_signal_flags[sig] = 0;
f(sig);
loop.ProcessReadObject(handle);
}

for (const auto &entry : loop.m_signals) {
if (loop.m_terminate_request)
return;
if (g_signal_flags[entry.first] == 0)
continue; // No signal
g_signal_flags[entry.first] = 0;
loop.ProcessSignal(entry.first);
}
}
#endif

MainLoop::MainLoop() {
#if HAVE_SYS_EVENT_H
m_kqueue = kqueue();
assert(m_kqueue >= 0);
#endif
}
MainLoop::~MainLoop() {
#if HAVE_SYS_EVENT_H
close(m_kqueue);
#endif
assert(m_read_fds.size() == 0);
assert(m_signals.size() == 0);
}
Expand Down Expand Up @@ -298,24 +281,30 @@ MainLoop::RegisterSignal(int signo, const Callback &callback,
new_action.sa_flags = SA_SIGINFO;
sigemptyset(&new_action.sa_mask);
sigaddset(&new_action.sa_mask, signo);

sigset_t old_set;
if (int ret = pthread_sigmask(SIG_BLOCK, &new_action.sa_mask, &old_set)) {
error.SetErrorStringWithFormat("pthread_sigmask failed with error %d\n",
ret);
return nullptr;
}

info.was_blocked = sigismember(&old_set, signo);
if (sigaction(signo, &new_action, &info.old_action) == -1) {
error.SetErrorToErrno();
if (!info.was_blocked)
pthread_sigmask(SIG_UNBLOCK, &new_action.sa_mask, nullptr);
return nullptr;
}
g_signal_flags[signo] = 0;

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

#if HAVE_SYS_EVENT_H
struct kevent ev;
EV_SET(&ev, signo, EVFILT_SIGNAL, EV_ADD, 0, 0, 0);
ret = kevent(m_kqueue, &ev, 1, nullptr, 0, nullptr);
assert(ret == 0);
#endif

// If we're using kqueue, the signal needs to be unblocked in order to recieve
// it. If using pselect/ppoll, we need to block it, and later unblock it as a
// part of the system call.
ret = pthread_sigmask(HAVE_SYS_EVENT_H ? SIG_UNBLOCK : SIG_BLOCK,
&new_action.sa_mask, &old_set);
assert(ret == 0 && "pthread_sigmask failed");
info.was_blocked = sigismember(&old_set, signo);
m_signals.insert({signo, info});
g_signal_flags[signo] = 0;

return SignalHandleUP(new SignalHandle(*this, signo));
#endif
Expand All @@ -331,7 +320,6 @@ void MainLoop::UnregisterSignal(int signo) {
#if SIGNAL_POLLING_UNSUPPORTED
Error("Signal polling is not supported on this platform.");
#else
// We undo the actions of RegisterSignal on a best-effort basis.
auto it = m_signals.find(signo);
assert(it != m_signals.end());

Expand All @@ -340,8 +328,17 @@ void MainLoop::UnregisterSignal(int signo) {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, signo);
pthread_sigmask(it->second.was_blocked ? SIG_BLOCK : SIG_UNBLOCK, &set,
nullptr);
int ret = pthread_sigmask(it->second.was_blocked ? SIG_BLOCK : SIG_UNBLOCK,
&set, nullptr);
assert(ret == 0);
(void)ret;

#if HAVE_SYS_EVENT_H
struct kevent ev;
EV_SET(&ev, signo, EVFILT_SIGNAL, EV_DELETE, 0, 0, 0);
ret = kevent(m_kqueue, &ev, 1, nullptr, 0, nullptr);
assert(ret == 0);
#endif

m_signals.erase(it);
#endif
Expand All @@ -351,32 +348,31 @@ Error MainLoop::Run() {
m_terminate_request = false;

Error error;
auto impl = RunImpl::Create(*this, error);
if (!impl)
return error;
RunImpl impl(*this);

// run until termination or until we run out of things to listen to
while (!m_terminate_request && (!m_read_fds.empty() || !m_signals.empty())) {

error = impl->Poll();
error = impl.Poll();
if (error.Fail())
return error;

impl->ForEachSignal([&](int sig) {
auto it = m_signals.find(sig);
if (it != m_signals.end())
it->second.callback(*this); // Do the work
});
if (m_terminate_request)
return Error();
impl.ProcessEvents();

impl->ForEachReadFD([&](int fd) {
auto it = m_read_fds.find(fd);
if (it != m_read_fds.end())
it->second(*this); // Do the work
});
if (m_terminate_request)
return Error();
}
return Error();
}

void MainLoop::ProcessSignal(int signo) {
auto it = m_signals.find(signo);
if (it != m_signals.end())
it->second.callback(*this); // Do the work
}

void MainLoop::ProcessReadObject(IOObject::WaitableHandle handle) {
auto it = m_read_fds.find(handle);
if (it != m_read_fds.end())
it->second(*this); // Do the work
}
1 change: 1 addition & 0 deletions lldb/unittests/Host/CMakeLists.txt
@@ -1,6 +1,7 @@
set (FILES
FileSpecTest.cpp
FileSystemTest.cpp
MainLoopTest.cpp
SocketAddressTest.cpp
SocketTest.cpp
SymbolsTest.cpp
Expand Down

0 comments on commit 4f1f641

Please sign in to comment.