sdbus::asio: service sd_event loop upon request

If requested, add in an asynchronous sd_event servicing mechanism so
sd_events can be used in conjunction with boost::asio events. Code
throughout the openbmc repositories use sd_events, especially for
timers. In some cases, we may want to add boost::asio event handling for
superior asynchronous eventing, but not want to rewrite all the existing
sd_event code. This gives us the best of both worlds, with a
low-overhead mechanism to handle the sd_events.

Change-Id: I3f8e2aafa3f733439e1494253698d17c2f3a2321
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
diff --git a/Makefile.am b/Makefile.am
index ab07920..ec552c3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -8,6 +8,7 @@
 	sdbusplus/asio/connection.hpp \
 	sdbusplus/asio/object_server.hpp \
 	sdbusplus/asio/detail/async_send_handler.hpp \
+	sdbusplus/asio/sd_event.hpp \
 	sdbusplus/bus.hpp \
 	sdbusplus/bus/match.hpp \
 	sdbusplus/sdbus.hpp \
diff --git a/example/asio-example.cpp b/example/asio-example.cpp
index 354c7b0..cced85d 100644
--- a/example/asio-example.cpp
+++ b/example/asio-example.cpp
@@ -4,8 +4,10 @@
 #include <iostream>
 #include <sdbusplus/asio/connection.hpp>
 #include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/asio/sd_event.hpp>
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/server.hpp>
+#include <sdbusplus/timer.hpp>
 
 int foo(int test)
 {
@@ -130,6 +132,15 @@
 
     iface->initialize();
     iface->set_property("int", 45);
+
+    // sd_events work too using the default event loop
+    phosphor::Timer t1([]() { std::cerr << "*** tock ***\n"; });
+    t1.start(std::chrono::microseconds(1000000));
+    phosphor::Timer t2([]() { std::cerr << "*** tick ***\n"; });
+    t2.start(std::chrono::microseconds(500000), true);
+    // add the sd_event wrapper to the io object
+    sdbusplus::asio::sd_event_wrapper sdEvents(io);
+
     io.run();
 
     return 0;
diff --git a/sdbusplus/asio/sd_event.hpp b/sdbusplus/asio/sd_event.hpp
new file mode 100644
index 0000000..b4a2ae3
--- /dev/null
+++ b/sdbusplus/asio/sd_event.hpp
@@ -0,0 +1,125 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+
+#include <systemd/sd-event.h>
+
+#include <boost/asio.hpp>
+
+namespace sdbusplus
+{
+
+namespace asio
+{
+/* A simple class to integrate the sd_event_loop into the boost::asio io_service
+ * in case a boost::asio user needs sd_events
+ */
+class sd_event_wrapper
+{
+  public:
+    sd_event_wrapper(boost::asio::io_service& io) :
+        evt(nullptr), descriptor(io), io(io)
+    {
+        sd_event_default(&evt);
+        if (evt)
+        {
+            descriptor.assign(sd_event_get_fd(evt));
+            async_run();
+        }
+    }
+    sd_event_wrapper(sd_event* evt, boost::asio::io_service& io) :
+        evt(evt), descriptor(io), io(io)
+    {
+        if (evt)
+        {
+            sd_event_ref(evt);
+            descriptor.assign(sd_event_get_fd(evt));
+            async_run();
+        }
+    }
+    ~sd_event_wrapper()
+    {
+        sd_event_unref(evt);
+    }
+    // process one event step in the queue
+    // return true if the queue is still alive
+    void run()
+    {
+        int ret;
+        int state = sd_event_get_state(evt);
+        switch (state)
+        {
+            case SD_EVENT_INITIAL:
+                ret = sd_event_prepare(evt);
+                if (ret > 0)
+                {
+                    async_run();
+                }
+                else if (ret == 0)
+                {
+                    async_wait();
+                }
+                break;
+            case SD_EVENT_ARMED:
+                ret = sd_event_wait(evt, 0);
+                if (ret >= 0)
+                {
+                    async_run();
+                }
+                break;
+            case SD_EVENT_PENDING:
+                ret = sd_event_dispatch(evt);
+                if (ret > 0)
+                {
+                    async_run();
+                }
+                break;
+            case SD_EVENT_FINISHED:
+                break;
+            default:
+                // throw something?
+                // doing nothing will break out of the async loop
+                break;
+        }
+    }
+    sd_event* get() const
+    {
+        return evt;
+    }
+
+  private:
+    void async_run()
+    {
+        io.post([this]() { run(); });
+    }
+    void async_wait()
+    {
+        descriptor.async_wait(boost::asio::posix::stream_descriptor::wait_read,
+                              [this](const boost::system::error_code& error) {
+                                  if (!error)
+                                  {
+                                      run();
+                                  }
+                              });
+    }
+    sd_event* evt;
+    boost::asio::posix::stream_descriptor descriptor;
+    boost::asio::io_service& io;
+};
+
+} // namespace asio
+
+} // namespace sdbusplus