daemon+rib: Merge nrd and nfd into a single process (separate threads)

Change-Id: I41952d5b8ee29f109130c570e0d13ccad6970d2f
Refs: #2489
diff --git a/daemon/main.cpp b/daemon/main.cpp
index eaa4e26..e6b9ae7 100644
--- a/daemon/main.cpp
+++ b/daemon/main.cpp
@@ -24,6 +24,7 @@
  */
 
 #include "nfd.hpp"
+#include "rib/nrd.hpp"
 
 #include "version.hpp"
 #include "core/global-io.hpp"
@@ -37,16 +38,33 @@
 #include <boost/program_options/variables_map.hpp>
 #include <boost/program_options/parsers.hpp>
 
+// boost::thread is used instead of std::thread to guarantee proper cleanup of thread local storage,
+// see http://www.boost.org/doc/libs/1_48_0/doc/html/thread/thread_local_storage.html
+#include <boost/thread.hpp>
+
+#include <atomic>
+#include <condition_variable>
+
 namespace nfd {
 
 NFD_LOG_INIT("NFD");
 
+/** \brief Executes NFD with RIB manager
+ *
+ *  NFD (main forwarding procedure) and RIB manager execute in two different threads.
+ *  Each thread has its own instances global io_service and global scheduler.
+ *
+ *  When either of the daemons fails, execution of non-failed daemon will be terminated as
+ *  well.  In other words, when NFD fails, RIB manager will be terminated; when RIB manager
+ *  fails, NFD will be terminated.
+ */
 class NfdRunner : noncopyable
 {
 public:
   explicit
   NfdRunner(const std::string& configFile)
-    : m_nfd(configFile, m_keyChain)
+    : m_nfd(configFile, m_nfdKeyChain)
+    , m_configFile(configFile)
     , m_terminationSignalSet(getGlobalIoService())
     , m_reloadSignalSet(getGlobalIoService())
   {
@@ -86,6 +104,91 @@
   }
 
   void
+  initialize()
+  {
+    m_nfd.initialize();
+  }
+
+  int
+  run()
+  {
+    /** \brief return value
+     *  A non-zero value is assigned when either NFD or RIB manager (running in a separate
+     *  thread) fails.
+     */
+    std::atomic_int retval(0);
+
+    boost::asio::io_service* const mainIo = &getGlobalIoService();
+    boost::asio::io_service* nrdIo = nullptr;
+
+    // Mutex and conditional variable to implement synchronization between main and RIB manager
+    // threads:
+    // - to block main thread until RIB manager thread starts and initializes nrdIo (to allow
+    //   stopping it later)
+    std::mutex m;
+    std::condition_variable cv;
+
+    std::string configFile = this->m_configFile; // c++11 lambda cannot capture member variables
+    boost::thread nrdThread([configFile, &retval, &nrdIo, mainIo, &cv, &m] {
+        {
+          std::lock_guard<std::mutex> lock(m);
+          nrdIo = &getGlobalIoService();
+          BOOST_ASSERT(nrdIo != mainIo);
+        }
+        cv.notify_all(); // notify that nrdIo has been assigned
+
+        try {
+          ndn::KeyChain nrdKeyChain;
+          // must be created inside a separate thread
+          rib::Nrd nrd(configFile, nrdKeyChain);
+          nrd.initialize();
+          getGlobalIoService().run(); // nrdIo is not thread-safe to use here
+        }
+        catch (const std::exception& e) {
+          NFD_LOG_FATAL(e.what());
+          retval = 6;
+          mainIo->stop();
+        }
+
+        {
+          std::lock_guard<std::mutex> lock(m);
+          nrdIo = nullptr;
+        }
+      });
+
+    {
+      // Wait to guarantee that nrdIo is properly initialized, so it can be used to terminate
+      // RIB manager thread.
+      std::unique_lock<std::mutex> lock(m);
+      cv.wait(lock, [&nrdIo] { return nrdIo != nullptr; });
+    }
+
+    try {
+      mainIo->run();
+    }
+    catch (const std::exception& e) {
+      NFD_LOG_FATAL(e.what());
+      retval = 4;
+    }
+    catch (const PrivilegeHelper::Error& e) {
+      NFD_LOG_FATAL(e.what());
+      retval = 5;
+    }
+
+    {
+      // nrdIo is guaranteed to be alive at this point
+      std::lock_guard<std::mutex> lock(m);
+      if (nrdIo != nullptr) {
+        nrdIo->stop();
+        nrdIo = nullptr;
+      }
+    }
+    nrdThread.join();
+
+    return retval;
+  }
+
+  void
   terminate(const boost::system::error_code& error, int signalNo)
   {
     if (error)
@@ -96,12 +199,6 @@
   }
 
   void
-  initialize()
-  {
-    m_nfd.initialize();
-  }
-
-  void
   reload(const boost::system::error_code& error, int signalNo)
   {
     if (error)
@@ -114,8 +211,10 @@
   }
 
 private:
-  ndn::KeyChain           m_keyChain;
+  ndn::KeyChain           m_nfdKeyChain;
   Nfd                     m_nfd;
+  std::string             m_configFile;
+
   boost::asio::signal_set m_terminationSignalSet;
   boost::asio::signal_set m_reloadSignalSet;
 };
@@ -192,17 +291,5 @@
     return 3;
   }
 
-  try {
-    getGlobalIoService().run();
-  }
-  catch (const std::exception& e) {
-    NFD_LOG_FATAL(e.what());
-    return 4;
-  }
-  catch (const PrivilegeHelper::Error& e) {
-    NFD_LOG_FATAL(e.what());
-    return 5;
-  }
-
-  return 0;
+  return runner.run();
 }