Make asio connection use composed operations

Composed operations [1] are the way that asio is attempting to support
multiple executor, coroutine, and async concepts in parallel.  This
commit is the first in hopefully a line of commits where we move to a
pattern that is more generally reusable, and reliant on fewer strict
types.

This is coming up because asio has removed support for
completion_token_type on the async_result object when used with
yield_context.  A discussion with the maintainers on slack makes it seem
like this was unintentional, but regardless, we can improve this code.

The primary upshot of this is that async_send is now moved to use
async_initiate, which abstracts away all the result and completion types
from the code.  This requires moving some of the details namespaces
structures to allow for construction without requiring the callback type
paramter.  In the same move, it moves what was previously a duplicated
callback into its own class, called do_unpack, which separates the two
concepts, and makes it more clear which is responsible for what.

In doing this move, the templates are now moved to be called
"CompletionToken", to be more in line with other asio async operations.
There are a number of cases where these template parameters already
weren't callbacks, so changing the naming to be inline with asio seems
appropriate.

This allows sdbusplus to support boost 1.80.0.

[1] https://www.boost.org/doc/libs/1_80_0/libs/beast/doc/html/beast/using_io/writing_composed_operations.html

Signed-off-by: Ed Tanous <edtanous@google.com>
Change-Id: I9d25040abbffbedb39f55eb93c0d7270091cdd63
diff --git a/include/sdbusplus/asio/connection.hpp b/include/sdbusplus/asio/connection.hpp
index a164ac1..4874f62 100644
--- a/include/sdbusplus/asio/connection.hpp
+++ b/include/sdbusplus/asio/connection.hpp
@@ -74,29 +74,19 @@
      *         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
+     *  @param[in] token- The completion token to execute upon completion;
      *
-     *  @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_t&))
-        async_send(message_t& m, MessageHandler&& handler, uint64_t timeout = 0)
+    template <typename CompletionToken>
+    inline auto async_send(message_t& m, CompletionToken&& token,
+                           uint64_t timeout = 0)
     {
-        boost::asio::async_completion<
-            MessageHandler, void(boost::system::error_code, message_t)>
-            init(handler);
-        detail::async_send_handler<typename boost::asio::async_result<
-            MessageHandler, void(boost::system::error_code,
-                                 message_t)>::completion_handler_type>(
-            std::move(init.completion_handler))(get(), m, timeout);
-        return init.result.get();
+        constexpr bool is_yield =
+            std::is_same_v<CompletionToken, boost::asio::yield_context>;
+        using return_t = std::conditional_t<is_yield, message_t, message_t&>;
+        using callback_t = void(boost::system::error_code, return_t);
+        return boost::asio::async_initiate<CompletionToken, callback_t>(
+            detail::async_send_handler(get(), m, timeout), token);
     }
 
     /** @brief Perform an asynchronous method call, with input parameter packing
diff --git a/include/sdbusplus/asio/detail/async_send_handler.hpp b/include/sdbusplus/asio/detail/async_send_handler.hpp
index 144a56a..0f40ce9 100644
--- a/include/sdbusplus/asio/detail/async_send_handler.hpp
+++ b/include/sdbusplus/asio/detail/async_send_handler.hpp
@@ -27,35 +27,31 @@
 {
 namespace detail
 {
-template <typename Handler>
-struct async_send_handler
+
+/* Class meant for converting a static callback, and void* userdata from sd-bus
+ * back into a structured class to be returned to the user.
+ */
+template <typename CompletionToken>
+struct unpack_userdata
 {
-    Handler handler_;
-    async_send_handler(Handler&& handler) : handler_(std::move(handler)) {}
-    async_send_handler(Handler& handler) : handler_(handler) {}
-    void operator()(sd_bus* conn, message_t& mesg, uint64_t timeout)
+    CompletionToken handler_;
+
+    static int do_unpack(sd_bus_message* mesg, void* userdata,
+                         sd_bus_error* /*error*/)
     {
-        async_send_handler* context = new async_send_handler(std::move(*this));
-        int ec = sd_bus_call_async(conn, NULL, mesg.get(), &callback, context,
-                                   timeout);
-        if (ec < 0)
-        {
-            // add a deleter to context because handler may throw
-            std::unique_ptr<async_send_handler> safe_context(context);
-            auto err =
-                make_error_code(static_cast<boost::system::errc::errc_t>(ec));
-            context->handler_(err, mesg);
-        }
-    }
-    static int callback(sd_bus_message* mesg, void* userdata,
-                        sd_bus_error* /*error*/)
-    {
-        if (userdata == nullptr || mesg == nullptr)
+        if (userdata == nullptr)
         {
             return -1;
         }
-        std::unique_ptr<async_send_handler> context(
-            static_cast<async_send_handler*>(userdata));
+
+        // Take RAII ownership of the pointer again
+        using self_t = unpack_userdata<CompletionToken>;
+        std::unique_ptr<self_t> context(static_cast<self_t*>(userdata));
+
+        if (mesg == nullptr)
+        {
+            return -1;
+        }
         message_t message(mesg);
         auto ec = make_error_code(
             static_cast<boost::system::errc::errc_t>(message.get_errno()));
@@ -63,6 +59,32 @@
         return 1;
     }
 };
+
+struct async_send_handler
+{
+    sd_bus* bus;
+    message_t& mesg;
+    uint64_t timeout;
+
+    template <typename CompletionToken>
+    void operator()(CompletionToken&& token)
+    {
+        using unpack_t = unpack_userdata<CompletionToken>;
+        auto context = std::make_unique<unpack_t>(std::move(token));
+        int ec = sd_bus_call_async(bus, NULL, mesg.get(), &unpack_t::do_unpack,
+                                   context.get(), timeout);
+        if (ec < 0)
+        {
+            auto err =
+                make_error_code(static_cast<boost::system::errc::errc_t>(ec));
+            context->handler_(err, mesg);
+            return;
+        }
+        // If the call succeeded, sd-bus owns the pointer now, so release it
+        // without freeing.
+        context.release();
+    }
+};
 } // namespace detail
 } // namespace asio
 } // namespace sdbusplus