[Lldb-commits] [lldb] r302133 - MainLoop: Add unit tests

Pavel Labath via lldb-commits lldb-commits at lists.llvm.org
Thu May 4 03:11:33 PDT 2017


Author: labath
Date: Thu May  4 05:11:33 2017
New Revision: 302133

URL: http://llvm.org/viewvc/llvm-project?rev=302133&view=rev
Log:
MainLoop: Add unit tests

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

Added:
    lldb/trunk/unittests/Host/MainLoopTest.cpp
Modified:
    lldb/trunk/include/lldb/Host/MainLoop.h
    lldb/trunk/source/Host/common/MainLoop.cpp
    lldb/trunk/unittests/Host/CMakeLists.txt

Modified: lldb/trunk/include/lldb/Host/MainLoop.h
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/Host/MainLoop.h?rev=302133&r1=302132&r2=302133&view=diff
==============================================================================
--- lldb/trunk/include/lldb/Host/MainLoop.h (original)
+++ lldb/trunk/include/lldb/Host/MainLoop.h Thu May  4 05:11:33 2017
@@ -42,6 +42,7 @@ private:
 public:
   typedef std::unique_ptr<SignalHandle> SignalHandleUP;
 
+  MainLoop();
   ~MainLoop() override;
 
   ReadHandleUP RegisterReadObject(const lldb::IOObjectSP &object_sp,
@@ -71,6 +72,9 @@ protected:
   void UnregisterSignal(int signo);
 
 private:
+  void ProcessReadObject(IOObject::WaitableHandle handle);
+  void ProcessSignal(int signo);
+
   class SignalHandle {
   public:
     ~SignalHandle() { m_mainloop.UnregisterSignal(m_signo); }
@@ -97,6 +101,9 @@ private:
 
   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;
 };
 

Modified: lldb/trunk/source/Host/common/MainLoop.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Host/common/MainLoop.cpp?rev=302133&r1=302132&r2=302133&view=diff
==============================================================================
--- lldb/trunk/source/Host/common/MainLoop.cpp (original)
+++ lldb/trunk/source/Host/common/MainLoop.cpp Thu May  4 05:11:33 2017
@@ -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)
@@ -65,92 +70,72 @@ static void SignalHandler(int signo, sig
 
 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() {
@@ -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) {
@@ -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();
@@ -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);
 }
@@ -298,24 +281,30 @@ MainLoop::RegisterSignal(int signo, cons
   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
@@ -331,7 +320,6 @@ void MainLoop::UnregisterSignal(int sign
 #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());
 
@@ -340,8 +328,17 @@ void MainLoop::UnregisterSignal(int sign
   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
@@ -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
+}

Modified: lldb/trunk/unittests/Host/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/unittests/Host/CMakeLists.txt?rev=302133&r1=302132&r2=302133&view=diff
==============================================================================
--- lldb/trunk/unittests/Host/CMakeLists.txt (original)
+++ lldb/trunk/unittests/Host/CMakeLists.txt Thu May  4 05:11:33 2017
@@ -1,6 +1,7 @@
 set (FILES
   FileSpecTest.cpp
   FileSystemTest.cpp
+  MainLoopTest.cpp
   SocketAddressTest.cpp
   SocketTest.cpp
   SymbolsTest.cpp

Added: lldb/trunk/unittests/Host/MainLoopTest.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/unittests/Host/MainLoopTest.cpp?rev=302133&view=auto
==============================================================================
--- lldb/trunk/unittests/Host/MainLoopTest.cpp (added)
+++ lldb/trunk/unittests/Host/MainLoopTest.cpp Thu May  4 05:11:33 2017
@@ -0,0 +1,120 @@
+//===-- MainLoopTest.cpp ----------------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/MainLoop.h"
+#include "lldb/Host/common/TCPSocket.h"
+#include "gtest/gtest.h"
+#include <future>
+
+using namespace lldb_private;
+
+namespace {
+class MainLoopTest : public testing::Test {
+public:
+  static void SetUpTestCase() {
+#ifdef _MSC_VER
+    WSADATA data;
+    ASSERT_EQ(0, WSAStartup(MAKEWORD(2, 2), &data));
+#endif
+  }
+
+  static void TearDownTestCase() {
+#ifdef _MSC_VER
+    ASSERT_EQ(0, WSACleanup());
+#endif
+  }
+
+  void SetUp() override {
+    bool child_processes_inherit = false;
+    Error error;
+    std::unique_ptr<TCPSocket> listen_socket_up(
+        new TCPSocket(true, child_processes_inherit));
+    ASSERT_TRUE(error.Success());
+    error = listen_socket_up->Listen("localhost:0", 5);
+    ASSERT_TRUE(error.Success());
+
+    Socket *accept_socket;
+    std::future<Error> accept_error = std::async(std::launch::async, [&] {
+      return listen_socket_up->Accept(accept_socket);
+    });
+
+    std::unique_ptr<TCPSocket> connect_socket_up(
+        new TCPSocket(true, child_processes_inherit));
+    error = connect_socket_up->Connect(
+        llvm::formatv("localhost:{0}", listen_socket_up->GetLocalPortNumber())
+            .str());
+    ASSERT_TRUE(error.Success());
+    ASSERT_TRUE(accept_error.get().Success());
+
+    callback_count = 0;
+    socketpair[0] = std::move(connect_socket_up);
+    socketpair[1].reset(accept_socket);
+  }
+
+  void TearDown() override {
+    socketpair[0].reset();
+    socketpair[1].reset();
+  }
+
+protected:
+  MainLoop::Callback make_callback() {
+    return [&](MainLoopBase &loop) {
+      ++callback_count;
+      loop.RequestTermination();
+    };
+  }
+  std::shared_ptr<Socket> socketpair[2];
+  unsigned callback_count;
+};
+} // namespace
+
+TEST_F(MainLoopTest, ReadObject) {
+  char X = 'X';
+  size_t len = sizeof(X);
+  ASSERT_TRUE(socketpair[0]->Write(&X, len).Success());
+
+  MainLoop loop;
+
+  Error error;
+  auto handle = loop.RegisterReadObject(socketpair[1], make_callback(), error);
+  ASSERT_TRUE(error.Success());
+  ASSERT_TRUE(handle);
+  ASSERT_TRUE(loop.Run().Success());
+  ASSERT_EQ(1u, callback_count);
+}
+
+TEST_F(MainLoopTest, TerminatesImmediately) {
+  char X = 'X';
+  size_t len = sizeof(X);
+  ASSERT_TRUE(socketpair[0]->Write(&X, len).Success());
+  ASSERT_TRUE(socketpair[1]->Write(&X, len).Success());
+
+  MainLoop loop;
+  Error error;
+  auto handle0 = loop.RegisterReadObject(socketpair[0], make_callback(), error);
+  ASSERT_TRUE(error.Success());
+  auto handle1 = loop.RegisterReadObject(socketpair[1], make_callback(), error);
+  ASSERT_TRUE(error.Success());
+
+  ASSERT_TRUE(loop.Run().Success());
+  ASSERT_EQ(1u, callback_count);
+}
+
+#ifdef LLVM_ON_UNIX
+TEST_F(MainLoopTest, Signal) {
+  MainLoop loop;
+  Error error;
+
+  auto handle = loop.RegisterSignal(SIGUSR1, make_callback(), error);
+  ASSERT_TRUE(error.Success());
+  kill(getpid(), SIGUSR1);
+  ASSERT_TRUE(loop.Run().Success());
+  ASSERT_EQ(1u, callback_count);
+}
+#endif




More information about the lldb-commits mailing list