diff options
| author | Vernon Mauery <vernon.mauery@linux.intel.com> | 2018-10-02 15:10:20 -0700 |
|---|---|---|
| committer | William A. Kennington III <wak@google.com> | 2018-10-24 20:33:17 +0000 |
| commit | 076d14af2b3e29e48471ffb7fab632317da0fc21 (patch) | |
| tree | 6dfcf8b81362f16b4ba2bef748fc3d22b66cca5a /example | |
| parent | 841d8d35173741654a771bec26569313b85af93b (diff) | |
| download | sdbusplus-076d14af2b3e29e48471ffb7fab632317da0fc21.tar.gz sdbusplus-076d14af2b3e29e48471ffb7fab632317da0fc21.zip | |
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>
Diffstat (limited to 'example')
| -rw-r--r-- | example/asio-example.cpp | 284 |
1 files changed, 223 insertions, 61 deletions
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_one( } } -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 << "oops: " << e.what() << "\n"; + } + if (!ec && testValue == 42) { - std::cout << "async call to TestMethod serialized via yield OK!\n"; + std::cout + << "yielding call to TestYieldFunction serialized via yield OK!\n"; } else { @@ -88,60 +167,12 @@ void do_start_async_method_call_two( } } -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 @@ int main() 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 @@ int main() 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; +} |

