async: add coroutine task support

Add sdbusplus::async::task<...> template which works with the
std::executors proposal (P2300) and test-cases.

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: Ie63791daeab90ae1cc3862bb30878531b1775fa7
diff --git a/include/sdbusplus/async/execution.hpp b/include/sdbusplus/async/execution.hpp
new file mode 100644
index 0000000..6120627
--- /dev/null
+++ b/include/sdbusplus/async/execution.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+// The upstream code has some warnings under GCC, so turn them off
+// as needed.
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnon-template-friend"
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#include <sdbusplus/async/stdexec/coroutine.hpp>
+#include <sdbusplus/async/stdexec/execution.hpp>
+#pragma GCC diagnostic pop
+
+// Add std::execution as sdbusplus::async::execution so that we can simplify
+// reference to any parts of it we use internally.
+namespace sdbusplus::async
+{
+namespace execution = std::execution;
+}
diff --git a/include/sdbusplus/async/task.hpp b/include/sdbusplus/async/task.hpp
new file mode 100644
index 0000000..e2618e4
--- /dev/null
+++ b/include/sdbusplus/async/task.hpp
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <sdbusplus/async/stdexec/task.hpp>
+
+namespace sdbusplus::async
+{
+template <typename T = void>
+using task = exec::task<T>;
+}
diff --git a/test/async/task.cpp b/test/async/task.cpp
new file mode 100644
index 0000000..34d9e65
--- /dev/null
+++ b/test/async/task.cpp
@@ -0,0 +1,78 @@
+#include <sdbusplus/async/execution.hpp>
+#include <sdbusplus/async/task.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace sdbusplus::async;
+
+TEST(Task, CoAwaitVoid)
+{
+    bool value = false;
+    auto t = [&]() -> task<> {
+        value = true;
+        co_return;
+    };
+
+    // Check to ensure the co-routine hasn't started executing yet.
+    EXPECT_FALSE(value);
+
+    // Run it and confirm the value is updated.
+    std::this_thread::sync_wait(t());
+    EXPECT_TRUE(value);
+}
+
+TEST(Task, CoAwaitInt)
+{
+    struct _
+    {
+        static auto one() -> task<int>
+        {
+            co_return 42;
+        }
+        static auto two(bool& executed) -> task<>
+        {
+            auto r = co_await one();
+            EXPECT_EQ(r, 42);
+            executed = true;
+            co_return;
+        }
+    };
+
+    // Add boolean to confirm that co-routine actually executed by the
+    // end.
+    bool executed = false;
+    std::this_thread::sync_wait(_::two(executed));
+    EXPECT_TRUE(executed);
+}
+
+TEST(Task, CoAwaitThrow)
+{
+    struct _
+    {
+        static auto one() -> task<>
+        {
+            throw std::logic_error("Failed");
+            co_return;
+        }
+
+        static auto two(bool& caught) -> task<>
+        {
+            try
+            {
+                co_await (one());
+            }
+            catch (const std::logic_error&)
+            {
+                caught = true;
+            }
+        }
+    };
+
+    // Ensure throws surface up.
+    EXPECT_THROW(std::this_thread::sync_wait(_::one()), std::logic_error);
+
+    // Ensure throws can be caught inside a co-routine.
+    bool caught = false;
+    std::this_thread::sync_wait(_::two(caught));
+    EXPECT_TRUE(caught);
+}
diff --git a/test/meson.build b/test/meson.build
index 4fa39d2..4d3114c 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -20,13 +20,14 @@
 endif
 
 tests = [
+    'async/task',
     'bus/list_names',
     'bus/match',
     'exception/sdbus_error',
     'message/append',
     'message/call',
-    'message/read',
     'message/native_types',
+    'message/read',
     'message/types',
     'timer',
     'unpack_properties',