diff options
| author | Vernon Mauery <vernon.mauery@linux.intel.com> | 2018-09-25 12:34:25 -0700 |
|---|---|---|
| committer | William A. Kennington III <wak@google.com> | 2018-10-12 19:27:01 +0000 |
| commit | 261e72b6fb5f4fbe95b68a5a6e3ee863025b3be3 (patch) | |
| tree | 1a50d69e906c3630d5744d4e40de893e44a218ee | |
| parent | fba332baf4bdd290c3640960ac7fc0d44b1a6bd5 (diff) | |
| download | sdbusplus-261e72b6fb5f4fbe95b68a5a6e3ee863025b3be3.tar.gz sdbusplus-261e72b6fb5f4fbe95b68a5a6e3ee863025b3be3.zip | |
Add coroutine support for sdbusplus::asio method calls
Using a coroutine to asynchronously execute method calls gives the best
of both worlds:
1) better readability because the code reads like synchronous code
2) better throughput because it is actually asynchronous
When passed in a boost::asio::yield_context, the sdbusplus::asio dbus
connection members async_send and async_method_call will execute
asynchronously using coroutines.
This also adds an example of how this works in the
example/asio-example.cpp file.
Change-Id: Ifb71b2c757ecbfd16b3be95bdefc45a701ca0d51
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
| -rw-r--r-- | example/Makefile.am | 2 | ||||
| -rw-r--r-- | example/asio-example.cpp | 75 | ||||
| -rw-r--r-- | sdbusplus/asio/connection.hpp | 97 | ||||
| -rw-r--r-- | sdbusplus/utility/type_traits.hpp | 7 |
4 files changed, 176 insertions, 5 deletions
diff --git a/example/Makefile.am b/example/Makefile.am index 86ab50a..c62fcf9 100644 --- a/example/Makefile.am +++ b/example/Makefile.am @@ -10,11 +10,13 @@ asio_example_CXXFLAGS = \ -DBOOST_ALL_NO_LIB \ -DBOOST_SYSTEM_NO_DEPRECATED \ -DBOOST_ERROR_CODE_HEADER_ONLY \ + -DBOOST_COROUTINES_NO_DEPRECATION_WARNING \ -I$(top_srcdir) asio_example_LDADD = \ $(SYSTEMD_LIBS) \ $(PTHREAD_LIBS) \ + -lboost_coroutine \ ../libsdbusplus.la asio_example_LDFLAGS = \ diff --git a/example/asio-example.cpp b/example/asio-example.cpp index cced85d..9819eba 100644 --- a/example/asio-example.cpp +++ b/example/asio-example.cpp @@ -1,4 +1,5 @@ #include <boost/asio.hpp> +#include <boost/asio/spawn.hpp> #include <chrono> #include <ctime> #include <iostream> @@ -9,6 +10,7 @@ #include <sdbusplus/server.hpp> #include <sdbusplus/timer.hpp> +using variant = sdbusplus::message::variant<int, std::string>; int foo(int test) { return ++test; @@ -24,6 +26,68 @@ int voidBar(void) return 42; } +void do_start_async_method_call_one( + std::shared_ptr<sdbusplus::asio::connection> conn, + boost::asio::yield_context yield) +{ + boost::system::error_code ec; + variant testValue; + conn->yield_method_call<>(yield[ec], "xyz.openbmc_project.asio-test", + "/xyz/openbmc_project/test", + "org.freedesktop.DBus.Properties", "Set", + "xyz.openbmc_project.test", "int", variant(24)); + testValue = conn->yield_method_call<variant>( + yield[ec], "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test", + "org.freedesktop.DBus.Properties", "Get", "xyz.openbmc_project.test", + "int"); + if (!ec && testValue.get<int>() == 24) + { + std::cout << "async call to Properties.Get serialized via yield OK!\n"; + } + else + { + std::cout << "ec = " << ec << ": " << testValue.get<int>() << "\n"; + } + conn->yield_method_call<void>( + yield[ec], "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test", + "org.freedesktop.DBus.Properties", "Set", "xyz.openbmc_project.test", + "int", variant(42)); + testValue = conn->yield_method_call<variant>( + yield[ec], "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test", + "org.freedesktop.DBus.Properties", "Get", "xyz.openbmc_project.test", + "int"); + if (!ec && testValue.get<int>() == 42) + { + std::cout << "async call to Properties.Get serialized via yield OK!\n"; + } + else + { + std::cout << "ec = " << ec << ": " << testValue.get<int>() << "\n"; + } +} + +void do_start_async_method_call_two( + std::shared_ptr<sdbusplus::asio::connection> conn, + boost::asio::yield_context yield) +{ + boost::system::error_code ec; + int32_t testCount; + std::string testValue; + std::tie(testCount, testValue) = + conn->yield_method_call<int32_t, std::string>( + yield[ec], "xyz.openbmc_project.asio-test", + "/xyz/openbmc_project/test", "xyz.openbmc_project.test", + "TestMethod", int32_t(42)); + if (!ec && testCount == 42 && testValue == "success: 42") + { + std::cout << "async call to TestMethod serialized via yield OK!\n"; + } + else + { + std::cout << "ec = " << ec << ": " << testValue << "\n"; + } +} + int main() { using GetSubTreeType = std::vector<std::pair< @@ -121,7 +185,8 @@ int main() // test method creation iface->register_method("TestMethod", [](const int32_t& callCount) { - return "success: " + std::to_string(callCount); + return std::make_tuple(callCount, + "success: " + std::to_string(callCount)); }); iface->register_method("TestFunction", foo); @@ -141,6 +206,14 @@ int main() // add the sd_event wrapper to the io object sdbusplus::asio::sd_event_wrapper sdEvents(io); + // set up a client to make an async call to the server + // using coroutines (userspace cooperative multitasking) + boost::asio::spawn(io, [conn](boost::asio::yield_context yield) { + do_start_async_method_call_one(conn, yield); + }); + boost::asio::spawn(io, [conn](boost::asio::yield_context yield) { + do_start_async_method_call_two(conn, yield); + }); io.run(); return 0; diff --git a/sdbusplus/asio/connection.hpp b/sdbusplus/asio/connection.hpp index 35bd853..0c7f80e 100644 --- a/sdbusplus/asio/connection.hpp +++ b/sdbusplus/asio/connection.hpp @@ -16,6 +16,7 @@ #pragma once #include <boost/asio.hpp> +#include <boost/asio/spawn.hpp> #include <boost/callable_traits.hpp> #include <chrono> #include <experimental/tuple> @@ -59,10 +60,23 @@ class connection : public sdbusplus::bus::bus socket.release(); } + /** @brief Perform an asynchronous send of a message, executing the handler + * upon return and return + * + * @param[in] m - A message ready to send + * @param[in] handler - handler to execute upon completion; this may be an + * asio::yield_context to execute asynchronously as a + * coroutine + * + * @return If a yield context is passed as the handler, the return type is + * a message. If a function object is passed in as the handler, + * the return type is the result of the handler registration, + * while the resulting message will get passed into the handler. + */ template <typename MessageHandler> inline BOOST_ASIO_INITFN_RESULT_TYPE(MessageHandler, void(boost::system::error_code, - message::message)) + message::message&)) async_send(message::message& m, MessageHandler&& handler) { boost::asio::async_completion< @@ -75,8 +89,27 @@ class connection : public sdbusplus::bus::bus return init.result.get(); } + /** @brief Perform an asynchronous method call, with input parameter packing + * and return value unpacking + * + * @param[in] handler - A function object that is to be called as a + * continuation for the async dbus method call. The + * arguments to parse on the return are deduced from + * the handler's signature and then passed in along + * with an error code. + * @param[in] service - The service to call. + * @param[in] objpath - The object's path for the call. + * @param[in] interf - The object's interface to call. + * @param[in] method - The object's method to call. + * @param[in] a... - Optional parameters for the method call. + * + * @return immediate return of the internal handler registration. The + * result of the actual asynchronous call will get unpacked from + * the message and passed into the handler when the call is + * complete. + */ template <typename MessageHandler, typename... InputArgs> - auto async_method_call(MessageHandler handler, const std::string& service, + void async_method_call(MessageHandler handler, const std::string& service, const std::string& objpath, const std::string& interf, const std::string& method, const InputArgs&... a) @@ -84,8 +117,8 @@ class connection : public sdbusplus::bus::bus message::message m = new_method_call(service.c_str(), objpath.c_str(), interf.c_str(), method.c_str()); m.append(a...); - return async_send(m, [handler](boost::system::error_code ec, - message::message& r) { + async_send(m, [handler](boost::system::error_code ec, + message::message& r) { using FunctionTuple = boost::callable_traits::args_t<MessageHandler>; using UnpackType = typename utility::strip_first_arg< @@ -107,6 +140,62 @@ class connection : public sdbusplus::bus::bus }); } + /** @brief Perform a yielding asynchronous method call, with input + * parameter packing and return value unpacking + * + * @param[in] yield - A yield context to async block upon. To catch errors + * for the call, call this function with 'yield[ec]', + * thus attaching an error code to the yield context + * @param[in] service - The service to call. + * @param[in] objpath - The object's path for the call. + * @param[in] interf - The object's interface to call. + * @param[in] method - The object's method to call. + * @param[in] a... - Optional parameters for the method call. + * + * @return Unpacked value of RetType + */ + template <typename... RetTypes, typename... InputArgs> + auto yield_method_call(boost::asio::yield_context yield, + const std::string& service, + const std::string& objpath, + const std::string& interf, const std::string& method, + const InputArgs&... a) + { + message::message m = new_method_call(service.c_str(), objpath.c_str(), + interf.c_str(), method.c_str()); + m.append(a...); + message::message r = async_send(m, yield); + if constexpr (sizeof...(RetTypes) == 0) + { + // void return + return; + } + else if constexpr (sizeof...(RetTypes) == 1) + { + if constexpr (std::is_same<utility::first_type<RetTypes...>, + void>::value) + { + return; + } + else + { + // single item return + utility::first_type<RetTypes...> responseData; + // this will throw if the signature of r != RetType + r.read(responseData); + return responseData; + } + } + else + { + // tuple of things to return + std::tuple<RetTypes...> responseData; + // this will throw if the signature of r != RetType + r.read(responseData); + return responseData; + } + } + private: boost::asio::io_service& io_; boost::asio::posix::stream_descriptor socket; diff --git a/sdbusplus/utility/type_traits.hpp b/sdbusplus/utility/type_traits.hpp index 2d60972..28fa31e 100644 --- a/sdbusplus/utility/type_traits.hpp +++ b/sdbusplus/utility/type_traits.hpp @@ -9,6 +9,13 @@ namespace sdbusplus namespace utility { +/** @brief Retrieve the first type from a parameter pack + * + * @tparam Types - the parameter pack + */ +template <typename... Types> +using first_type = std::tuple_element_t<0, std::tuple<Types...>>; + /** @brief Convert T[N] to T* if is_same<Tbase,T> * * @tparam Tbase - The base type expected. |

