Add sdbusplus::asio coroutine method handling
Adding the server-side of the coroutine path allows yielding
asynchronous method handling via coroutines. This means that a method
handler can call a yielding dbus call (or other asio-based asynchronous
call) without blocking the rest of the process.
The call path might look something like this:
service.thing/object/path/interface.my-method()
- do something
- yield_method_call(other.service, /other/path,
other.interface, other-method)
<yields to other coroutine>
execute other code in another context
<returns some time later with dbus call's response>
- use response from other method
<- return my-method response
This also changes the asio-example, pulling it apart into a
client/server model so it is more clear about how to use the yielding
async method handling and yielding async method calls.
Change-Id: I23ccf7a9a8dff787be78929959c1f018280a0392
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
diff --git a/example/asio-example.cpp b/example/asio-example.cpp
index 9819eba..b4f6265 100644
--- a/example/asio-example.cpp
+++ b/example/asio-example.cpp
@@ -7,22 +7,47 @@
#include <sdbusplus/asio/object_server.hpp>
#include <sdbusplus/asio/sd_event.hpp>
#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
#include <sdbusplus/server.hpp>
#include <sdbusplus/timer.hpp>
using variant = sdbusplus::message::variant<int, std::string>;
+
int foo(int test)
{
+ std::cout << "foo(" << test << ") -> " << (test + 1) << "\n";
return ++test;
}
+// called from coroutine context, can make yielding dbus calls
+int fooYield(boost::asio::yield_context yield,
+ std::shared_ptr<sdbusplus::asio::connection> conn, int test)
+{
+ // fetch the real value from testFunction
+ boost::system::error_code ec;
+ std::cout << "fooYield(yield, " << test << ")...\n";
+ int testCount = conn->yield_method_call<int>(
+ yield[ec], "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test",
+ "xyz.openbmc_project.test", "TestFunction", test);
+ if (ec || testCount != (test + 1))
+ {
+ std::cout << "call to foo failed: ec = " << ec << '\n';
+ return -1;
+ }
+ std::cout << "yielding call to foo OK! (-> " << testCount << ")\n";
+ return testCount;
+}
+
int methodWithMessage(sdbusplus::message::message& m, int test)
{
+ std::cout << "methodWithMessage(m, " << test << ") -> " << (test + 1)
+ << "\n";
return ++test;
}
int voidBar(void)
{
+ std::cout << "voidBar() -> 42\n";
return 42;
}
@@ -66,21 +91,75 @@
}
}
-void do_start_async_method_call_two(
- std::shared_ptr<sdbusplus::asio::connection> conn,
- boost::asio::yield_context yield)
+void do_start_async_ipmi_call(std::shared_ptr<sdbusplus::asio::connection> conn,
+ boost::asio::yield_context yield)
+{
+ auto method = conn->new_method_call("xyz.openbmc_project.asio-test",
+ "/xyz/openbmc_project/test",
+ "xyz.openbmc_project.test", "execute");
+ constexpr uint8_t netFn = 6;
+ constexpr uint8_t lun = 0;
+ constexpr uint8_t cmd = 1;
+ std::map<std::string, variant> options = {{"username", variant("admin")},
+ {"privilege", variant(4)}};
+ std::vector<uint8_t> commandData = {4, 3, 2, 1};
+ method.append(netFn, lun, cmd, commandData, options);
+ boost::system::error_code ec;
+ sdbusplus::message::message reply = conn->async_send(method, yield[ec]);
+ std::tuple<uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>
+ tupleOut;
+ try
+ {
+ reply.read(tupleOut);
+ }
+ catch (const sdbusplus::exception::SdBusError& e)
+ {
+ std::cerr << "failed to unpack; sig is " << reply.get_signature()
+ << "\n";
+ }
+ auto& [rnetFn, rlun, rcmd, cc, responseData] = tupleOut;
+ std::vector<uint8_t> expRsp = {1, 2, 3, 4};
+ if (rnetFn == uint8_t(netFn + 1) && rlun == lun && rcmd == cmd && cc == 0 &&
+ responseData == expRsp)
+ {
+ std::cerr << "ipmi call returns OK!\n";
+ }
+ else
+ {
+ std::cerr << "ipmi call returns unexpected response\n";
+ }
+}
+
+auto ipmiInterface(boost::asio::yield_context yield, uint8_t netFn, uint8_t lun,
+ uint8_t cmd, std::vector<uint8_t>& data,
+ const std::map<std::string, variant>& options)
+{
+ std::vector<uint8_t> reply = {1, 2, 3, 4};
+ uint8_t cc = 0;
+ std::cerr << "ipmiInterface:execute(" << int(netFn) << int(cmd) << ")\n";
+ return std::make_tuple(uint8_t(netFn + 1), lun, cmd, cc, reply);
+}
+
+void do_start_async_to_yield(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>(
+ int testValue = 0;
+ try
+ {
+ testValue = conn->yield_method_call<int>(
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")
+ "TestYieldFunction", int(41));
+ }
+ catch (sdbusplus::exception::SdBusError& e)
{
- std::cout << "async call to TestMethod serialized via yield OK!\n";
+ std::cout << "oops: " << e.what() << "\n";
+ }
+ if (!ec && testValue == 42)
+ {
+ std::cout
+ << "yielding call to TestYieldFunction serialized via yield OK!\n";
}
else
{
@@ -88,60 +167,12 @@
}
}
-int main()
+int server()
{
- using GetSubTreeType = std::vector<std::pair<
- std::string,
- std::vector<std::pair<std::string, std::vector<std::string>>>>>;
- using message = sdbusplus::message::message;
// setup connection to dbus
boost::asio::io_service io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
- // test async method call and async send
- auto mesg =
- conn->new_method_call("xyz.openbmc_project.ObjectMapper",
- "/xyz/openbmc_project/object_mapper",
- "xyz.openbmc_project.ObjectMapper", "GetSubTree");
-
- static const auto depth = 2;
- static const std::vector<std::string> interfaces = {
- "xyz.openbmc_project.Sensor.Value"};
- mesg.append("/xyz/openbmc_project/Sensors", depth, interfaces);
-
- conn->async_send(mesg, [](boost::system::error_code ec, message& ret) {
- std::cout << "async_send callback\n";
- if (ec || ret.is_method_error())
- {
- std::cerr << "error with async_send\n";
- return;
- }
- GetSubTreeType data;
- ret.read(data);
- for (auto& item : data)
- {
- std::cout << item.first << "\n";
- }
- });
-
- conn->async_method_call(
- [](boost::system::error_code ec, GetSubTreeType& subtree) {
- std::cout << "async_method_call callback\n";
- if (ec)
- {
- std::cerr << "error with async_method_call\n";
- return;
- }
- for (auto& item : subtree)
- {
- std::cout << item.first << "\n";
- }
- },
- "xyz.openbmc_project.ObjectMapper",
- "/xyz/openbmc_project/object_mapper",
- "xyz.openbmc_project.ObjectMapper", "GetSubTree",
- "/org/openbmc/control", 2, std::vector<std::string>());
-
// test object server
conn->request_name("xyz.openbmc_project.asio-test");
auto server = sdbusplus::asio::object_server(conn);
@@ -191,12 +222,99 @@
iface->register_method("TestFunction", foo);
+ // fooYield has boost::asio::yield_context as first argument
+ // so will be executed in coroutine context if called
+ iface->register_method("TestYieldFunction",
+ [conn](boost::asio::yield_context yield, int val) {
+ return fooYield(yield, conn, val);
+ });
+
iface->register_method("TestMethodWithMessage", methodWithMessage);
iface->register_method("VoidFunctionReturnsInt", voidBar);
+ iface->register_method("execute", ipmiInterface);
+
iface->initialize();
- iface->set_property("int", 45);
+
+ io.run();
+
+ return 0;
+}
+
+int client()
+{
+ using GetSubTreeType = std::vector<std::pair<
+ std::string,
+ std::vector<std::pair<std::string, std::vector<std::string>>>>>;
+ using message = sdbusplus::message::message;
+
+ // setup connection to dbus
+ boost::asio::io_service io;
+ auto conn = std::make_shared<sdbusplus::asio::connection>(io);
+
+ int ready = 0;
+ while (!ready)
+ {
+ auto readyMsg = conn->new_method_call(
+ "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test",
+ "xyz.openbmc_project.test", "VoidFunctionReturnsInt");
+ try
+ {
+ message intMsg = conn->call(readyMsg);
+ intMsg.read(ready);
+ }
+ catch (sdbusplus::exception::SdBusError& e)
+ {
+ ready = 0;
+ // pause to give the server a chance to start up
+ usleep(10000);
+ }
+ }
+
+ // test async method call and async send
+ auto mesg =
+ conn->new_method_call("xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetSubTree");
+
+ static const auto depth = 2;
+ static const std::vector<std::string> interfaces = {
+ "xyz.openbmc_project.Sensor.Value"};
+ mesg.append("/xyz/openbmc_project/Sensors", depth, interfaces);
+
+ conn->async_send(mesg, [](boost::system::error_code ec, message& ret) {
+ std::cout << "async_send callback\n";
+ if (ec || ret.is_method_error())
+ {
+ std::cerr << "error with async_send\n";
+ return;
+ }
+ GetSubTreeType data;
+ ret.read(data);
+ for (auto& item : data)
+ {
+ std::cout << item.first << "\n";
+ }
+ });
+
+ conn->async_method_call(
+ [](boost::system::error_code ec, GetSubTreeType& subtree) {
+ std::cout << "async_method_call callback\n";
+ if (ec)
+ {
+ std::cerr << "error with async_method_call\n";
+ return;
+ }
+ for (auto& item : subtree)
+ {
+ std::cout << item.first << "\n";
+ }
+ },
+ "xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetSubTree",
+ "/org/openbmc/control", 2, std::vector<std::string>());
// sd_events work too using the default event loop
phosphor::Timer t1([]() { std::cerr << "*** tock ***\n"; });
@@ -212,9 +330,53 @@
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);
+ do_start_async_ipmi_call(conn, yield);
});
+ boost::asio::spawn(io, [conn](boost::asio::yield_context yield) {
+ do_start_async_to_yield(conn, yield);
+ });
+
+ conn->async_method_call(
+ [](boost::system::error_code ec, int32_t testValue) {
+ if (ec)
+ {
+ std::cerr << "TestYieldFunction returned error with "
+ "async_method_call (ec = "
+ << ec << ")\n";
+ return;
+ }
+ std::cout << "TestYieldFunction return " << testValue << "\n";
+ },
+ "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test",
+ "xyz.openbmc_project.test", "TestYieldFunction", int32_t(41));
io.run();
return 0;
}
+
+int main(int argc, const char* argv[])
+{
+ if (argc == 1)
+ {
+ int pid = fork();
+ if (pid == 0)
+ {
+ return client();
+ }
+ else if (pid > 0)
+ {
+ return server();
+ }
+ return pid;
+ }
+ if (std::string(argv[1]) == "--server")
+ {
+ return server();
+ }
+ if (std::string(argv[1]) == "--client")
+ {
+ return client();
+ }
+ std::cout << "usage: " << argv[0] << " [--server | --client]\n";
+ return -1;
+}