diff --git a/modules/drogon/drogon/lib/inc/drogon/HttpClient.h b/modules/drogon/drogon/lib/inc/drogon/HttpClient.h index 92ddf57..fc86ca4 100644 --- a/modules/drogon/drogon/lib/inc/drogon/HttpClient.h +++ b/modules/drogon/drogon/lib/inc/drogon/HttpClient.h @@ -25,30 +25,9 @@ #include #include -#ifdef __cpp_impl_coroutine -#include -#endif - namespace drogon { class HttpClient; using HttpClientPtr = std::shared_ptr; -#ifdef __cpp_impl_coroutine -namespace internal { -struct HttpRespAwaiter : public CallbackAwaiter { - HttpRespAwaiter(HttpClient *client, HttpRequestPtr req, double timeout) : - client_(client), req_(std::move(req)), timeout_(timeout) { - } - - void await_suspend(std::coroutine_handle<> handle); - -private: - HttpClient *client_; - HttpRequestPtr req_; - double timeout_; -}; - -} // namespace internal -#endif /// Asynchronous http client /** @@ -133,25 +112,6 @@ public: return f.get(); } -#ifdef __cpp_impl_coroutine - /** - * @brief Send a request via coroutines to the server and return an - * awaiter what could be `co_await`-ed to retrieve the response - * (HttpResponsePtr) - * - * @param req - * @param timeout In seconds. If the response is not received within the - * timeout, A `std::runtime_error` with the message "Timeout" is thrown. - * The zero value by default disables the timeout. - * - * @return internal::HttpRespAwaiter. Await on it to get the response - */ - internal::HttpRespAwaiter sendRequestCoro(HttpRequestPtr req, - double timeout = 0) { - return internal::HttpRespAwaiter(this, std::move(req), timeout); - } -#endif - /// Set the pipelining depth, which is the number of requests that are not /// responding. /** @@ -272,34 +232,4 @@ protected: HttpClient() = default; }; -#ifdef __cpp_impl_coroutine -inline void internal::HttpRespAwaiter::await_suspend( - std::coroutine_handle<> handle) { - assert(client_ != nullptr); - assert(req_ != nullptr); - client_->sendRequest( - req_, - [handle = std::move(handle), this](ReqResult result, - const HttpResponsePtr &resp) { - if (result == ReqResult::Ok) - setValue(resp); - else { - std::string reason; - if (result == ReqResult::BadResponse) - reason = "BadResponse"; - else if (result == ReqResult::NetworkFailure) - reason = "NetworkFailure"; - else if (result == ReqResult::BadServerAddress) - reason = "BadServerAddress"; - else if (result == ReqResult::Timeout) - reason = "Timeout"; - setException( - std::make_exception_ptr(std::runtime_error(reason))); - } - handle.resume(); - }, - timeout_); -} -#endif - } // namespace drogon diff --git a/modules/drogon/drogon/lib/inc/drogon/WebSocketClient.h b/modules/drogon/drogon/lib/inc/drogon/WebSocketClient.h index 3635320..eccd530 100644 --- a/modules/drogon/drogon/lib/inc/drogon/WebSocketClient.h +++ b/modules/drogon/drogon/lib/inc/drogon/WebSocketClient.h @@ -18,9 +18,6 @@ #include #include #include -#ifdef __cpp_impl_coroutine -#include -#endif #include #include #include @@ -32,23 +29,6 @@ using WebSocketClientPtr = std::shared_ptr; using WebSocketRequestCallback = std::function< void(ReqResult, const HttpResponsePtr &, const WebSocketClientPtr &)>; -#ifdef __cpp_impl_coroutine -namespace internal { -struct WebSocketConnectionAwaiter : public CallbackAwaiter { - WebSocketConnectionAwaiter(WebSocketClient *client, HttpRequestPtr req) : - client_(client), req_(std::move(req)) { - } - - void await_suspend(std::coroutine_handle<> handle); - -private: - WebSocketClient *client_; - HttpRequestPtr req_; -}; - -} // namespace internal -#endif - /** * @brief WebSocket client abstract class * @@ -85,52 +65,6 @@ public: virtual void connectToServer(const HttpRequestPtr &request, const WebSocketRequestCallback &callback) = 0; -#ifdef __cpp_impl_coroutine - /** - * @brief Set messages handler. When a message is recieved from the server, - * the callback is called. - * - * @param callback The function to call when a message is received. - */ - void setAsyncMessageHandler( - const std::function(std::string &&message, - const WebSocketClientPtr &, - const WebSocketMessageType &)> &callback) { - setMessageHandler([callback](std::string &&message, - const WebSocketClientPtr &client, - const WebSocketMessageType &type) -> void { - [callback](std::string &&message, - const WebSocketClientPtr client, - const WebSocketMessageType type) -> AsyncTask { - co_await callback(std::move(message), client, type); - }(std::move(message), client, type); - }); - } - - /// Set the connection closing handler. When the connection is established - /// or closed, the @param callback is called with a bool parameter. - - /** - * @brief Set the connection closing handler. When the websocket connection - * is closed, the callback is called - * - * @param callback The function to call when the connection is closed. - */ - void setAsyncConnectionClosedHandler( - const std::function(const WebSocketClientPtr &)> &callback) { - setConnectionClosedHandler( - [callback](const WebSocketClientPtr &client) { - [=]() -> AsyncTask { co_await callback(client); }(); - }); - } - - /// Connect to the server. - internal::WebSocketConnectionAwaiter connectToServerCoro( - const HttpRequestPtr &request) { - return internal::WebSocketConnectionAwaiter(this, request); - } -#endif - /// Get the event loop of the client; virtual trantor::EventLoop *getLoop() = 0; @@ -194,33 +128,4 @@ public: virtual ~WebSocketClient() = default; }; -#ifdef __cpp_impl_coroutine -inline void internal::WebSocketConnectionAwaiter::await_suspend( - std::coroutine_handle<> handle) { - client_->connectToServer(req_, - [this, handle](ReqResult result, - const HttpResponsePtr &resp, - const WebSocketClientPtr &) { - if (result == ReqResult::Ok) - setValue(resp); - else { - std::string reason; - if (result == ReqResult::BadResponse) - reason = "BadResponse"; - else if (result == - ReqResult::NetworkFailure) - reason = "NetworkFailure"; - else if (result == - ReqResult::BadServerAddress) - reason = "BadServerAddress"; - else if (result == ReqResult::Timeout) - reason = "Timeout"; - setException(std::make_exception_ptr( - std::runtime_error(reason))); - } - handle.resume(); - }); -} -#endif - } // namespace drogon diff --git a/modules/drogon/drogon/lib/inc/drogon/utils/FunctionTraits.h b/modules/drogon/drogon/lib/inc/drogon/utils/FunctionTraits.h index 623dc47..fd15524 100644 --- a/modules/drogon/drogon/lib/inc/drogon/utils/FunctionTraits.h +++ b/modules/drogon/drogon/lib/inc/drogon/utils/FunctionTraits.h @@ -19,10 +19,6 @@ #include #include -#ifdef __cpp_impl_coroutine -#include -#endif - namespace drogon { class HttpRequest; class HttpResponse; @@ -30,14 +26,11 @@ using HttpRequestPtr = std::shared_ptr; using HttpResponsePtr = std::shared_ptr; namespace internal { -#ifdef __cpp_impl_coroutine -template -using resumable_type = is_resumable; -#else + + template struct resumable_type : std::false_type { }; -#endif template struct FunctionTraits; @@ -102,41 +95,6 @@ struct FunctionTraits< using class_type = void; }; -#ifdef __cpp_impl_coroutine -template -struct FunctionTraits< - AsyncTask (*)(HttpRequestPtr req, - std::function callback, - Arguments...)> : FunctionTraits { - static const bool isHTTPFunction = true; - static const bool isCoroutine = true; - using class_type = void; - using first_param_type = HttpRequestPtr; - using return_type = AsyncTask; -}; -template -struct FunctionTraits< - Task<> (*)(HttpRequestPtr req, - std::function callback, - Arguments...)> : FunctionTraits { - static const bool isHTTPFunction = true; - static const bool isCoroutine = true; - using class_type = void; - using first_param_type = HttpRequestPtr; - using return_type = Task<>; -}; -template -struct FunctionTraits (*)(HttpRequestPtr req, - Arguments...)> - : FunctionTraits { - static const bool isHTTPFunction = true; - static const bool isCoroutine = true; - using class_type = void; - using first_param_type = HttpRequestPtr; - using return_type = Task; -}; -#endif - template struct FunctionTraits< ReturnType (*)(HttpRequestPtr &&req, diff --git a/modules/drogon/drogon/lib/inc/drogon/utils/coroutine.h b/modules/drogon/drogon/lib/inc/drogon/utils/coroutine.h deleted file mode 100644 index 11ca646..0000000 --- a/modules/drogon/drogon/lib/inc/drogon/utils/coroutine.h +++ /dev/null @@ -1,594 +0,0 @@ -/** - * - * @file coroutine.h - * @author Martin Chang - * - * Copyright 2021, Martin Chang. All rights reserved. - * https://github.com/an-tao/drogon - * Use of this source code is governed by a MIT license - * that can be found in the License file. - * - * Drogon - * - */ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace drogon { -namespace internal { -template -auto getAwaiterImpl(T &&value) noexcept( - noexcept(static_cast(value).operator co_await())) - -> decltype(static_cast(value).operator co_await()) { - return static_cast(value).operator co_await(); -} - -template -auto getAwaiterImpl(T &&value) noexcept( - noexcept(operator co_await(static_cast(value)))) - -> decltype(operator co_await(static_cast(value))) { - return operator co_await(static_cast(value)); -} - -template -auto getAwaiter(T &&value) noexcept( - noexcept(getAwaiterImpl(static_cast(value)))) - -> decltype(getAwaiterImpl(static_cast(value))) { - return getAwaiterImpl(static_cast(value)); -} - -} // end namespace internal - -template -struct await_result { - using awaiter_t = decltype(internal::getAwaiter(std::declval())); - using type = decltype(std::declval().await_resume()); -}; - -template -using await_result_t = typename await_result::type; - -template > -struct is_awaitable : std::false_type { -}; - -template -struct is_awaitable< - T, - std::void_t()))> > - : std::true_type { -}; - -template -constexpr bool is_awaitable_v = is_awaitable::value; - -struct final_awaiter { - bool await_ready() noexcept { - return false; - } - template - auto await_suspend(std::coroutine_handle handle) noexcept { - return handle.promise().continuation_; - } - void await_resume() noexcept { - } -}; - -template -struct [[nodiscard]] Task { - struct promise_type; - using handle_type = std::coroutine_handle; - - Task(handle_type h) : - coro_(h) { - } - Task(const Task &) = delete; - Task(Task &&other) { - coro_ = other.coro_; - other.coro_ = nullptr; - } - ~Task() { - if (coro_) - coro_.destroy(); - } - Task &operator=(const Task &) = delete; - Task &operator=(Task &&other) { - if (std::addressof(other) == this) - return *this; - if (coro_) - coro_.destroy(); - - coro_ = other.coro_; - other.coro_ = nullptr; - return *this; - } - - struct promise_type { - Task get_return_object() { - return Task{ handle_type::from_promise(*this) }; - } - std::suspend_always initial_suspend() { - return {}; - } - void return_value(const T &v) { - value = v; - } - void return_value(T &&v) { - value = std::move(v); - } - - auto final_suspend() noexcept { - return final_awaiter{}; - } - - void unhandled_exception() { - exception_ = std::current_exception(); - } - - T &&result() && { - if (exception_ != nullptr) - std::rethrow_exception(exception_); - assert(value.has_value() == true); - return std::move(value.value()); - } - - T &result() & { - if (exception_ != nullptr) - std::rethrow_exception(exception_); - assert(value.has_value() == true); - return value.value(); - } - - void setContinuation(std::coroutine_handle<> handle) { - continuation_ = handle; - } - - optional value; - std::exception_ptr exception_; - std::coroutine_handle<> continuation_; - }; - bool await_ready() const { - return !coro_ || coro_.done(); - } - std::coroutine_handle<> await_suspend(std::coroutine_handle<> awaiting) { - coro_.promise().setContinuation(awaiting); - return coro_; - } - - auto operator co_await() const &noexcept { - struct awaiter { - public: - explicit awaiter(handle_type coro) : - coro_(coro) { - } - bool await_ready() noexcept { - return !coro_ || coro_.done(); - } - auto await_suspend(std::coroutine_handle<> handle) noexcept { - coro_.promise().setContinuation(handle); - return coro_; - } - T await_resume() { - auto &&v = coro_.promise().result(); - return std::move(v); - } - - private: - handle_type coro_; - }; - return awaiter(coro_); - } - - auto operator co_await() const &&noexcept { - struct awaiter { - public: - explicit awaiter(handle_type coro) : - coro_(coro) { - } - bool await_ready() noexcept { - return !coro_ || coro_.done(); - } - auto await_suspend(std::coroutine_handle<> handle) noexcept { - coro_.promise().setContinuation(handle); - return coro_; - } - T await_resume() { - return std::move(coro_.promise().result()); - } - - private: - handle_type coro_; - }; - return awaiter(coro_); - } - handle_type coro_; -}; - -template <> -struct [[nodiscard]] Task { - struct promise_type; - using handle_type = std::coroutine_handle; - - Task(handle_type handle) : - coro_(handle) { - } - Task(const Task &) = delete; - Task(Task &&other) { - coro_ = other.coro_; - other.coro_ = nullptr; - } - ~Task() { - if (coro_) - coro_.destroy(); - } - Task &operator=(const Task &) = delete; - Task &operator=(Task &&other) { - if (std::addressof(other) == this) - return *this; - if (coro_) - coro_.destroy(); - - coro_ = other.coro_; - other.coro_ = nullptr; - return *this; - } - - struct promise_type { - Task<> get_return_object() { - return Task<>{ handle_type::from_promise(*this) }; - } - std::suspend_always initial_suspend() { - return {}; - } - void return_void() { - } - auto final_suspend() noexcept { - return final_awaiter{}; - } - void unhandled_exception() { - exception_ = std::current_exception(); - } - void result() { - if (exception_ != nullptr) - std::rethrow_exception(exception_); - } - void setContinuation(std::coroutine_handle<> handle) { - continuation_ = handle; - } - std::exception_ptr exception_; - std::coroutine_handle<> continuation_; - }; - bool await_ready() { - return coro_.done(); - } - std::coroutine_handle<> await_suspend(std::coroutine_handle<> awaiting) { - coro_.promise().setContinuation(awaiting); - return coro_; - } - auto operator co_await() const &noexcept { - struct awaiter { - public: - explicit awaiter(handle_type coro) : - coro_(coro) { - } - bool await_ready() noexcept { - return !coro_ || coro_.done(); - } - auto await_suspend(std::coroutine_handle<> handle) noexcept { - coro_.promise().setContinuation(handle); - return coro_; - } - auto await_resume() { - coro_.promise().result(); - } - - private: - handle_type coro_; - }; - return awaiter(coro_); - } - - auto operator co_await() const &&noexcept { - struct awaiter { - public: - explicit awaiter(handle_type coro) : - coro_(coro) { - } - bool await_ready() noexcept { - return false; - } - auto await_suspend(std::coroutine_handle<> handle) noexcept { - coro_.promise().setContinuation(handle); - return coro_; - } - void await_resume() { - coro_.promise().result(); - } - - private: - handle_type coro_; - }; - return awaiter(coro_); - } - handle_type coro_; -}; - -/// Fires a coroutine and doesn't force waiting nor deallocates upon promise -/// destructs -// NOTE: AsyncTask is designed to be not awaitable. And kills the entire process -// if exception escaped. -struct AsyncTask { - struct promise_type; - using handle_type = std::coroutine_handle; - - AsyncTask() = default; - - AsyncTask(handle_type h) : - coro_(h) { - } - - AsyncTask(const AsyncTask &) = delete; - - AsyncTask &operator=(const AsyncTask &) = delete; - AsyncTask &operator=(AsyncTask &&other) { - if (std::addressof(other) == this) - return *this; - - coro_ = other.coro_; - other.coro_ = nullptr; - return *this; - } - - struct promise_type { - std::coroutine_handle<> continuation_; - - AsyncTask get_return_object() noexcept { - return { std::coroutine_handle::from_promise(*this) }; - } - - std::suspend_never initial_suspend() const noexcept { - return {}; - } - - void unhandled_exception() { - LOG_FATAL << "Exception escaping AsyncTask."; - std::terminate(); - } - - void return_void() noexcept { - } - - void setContinuation(std::coroutine_handle<> handle) { - continuation_ = handle; - } - - auto final_suspend() const noexcept { - // Can't simply use suspend_never because we need symmetric transfer - struct awaiter final { - bool await_ready() const noexcept { - return true; - } - - auto await_suspend( - std::coroutine_handle coro) const noexcept { - return coro.promise().continuation_; - } - - void await_resume() const noexcept { - } - }; - return awaiter{}; - } - }; - bool await_ready() const noexcept { - return coro_.done(); - } - - void await_resume() const noexcept { - } - - auto await_suspend(std::coroutine_handle<> coroutine) noexcept { - coro_.promise().setContinuation(coroutine); - return coro_; - } - - handle_type coro_; -}; - -/// Helper class that provides the infrastructure for turning callback into -/// coroutines -// The user is responsible to fill in `await_suspend()` and constructors. -template -struct CallbackAwaiter { - bool await_ready() noexcept { - return false; - } - - const T &await_resume() const noexcept(false) { - // await_resume() should always be called after co_await - // (await_suspend()) is called. Therefore the value should always be set - // (or there's an exception) - assert(result_.has_value() == true || exception_ != nullptr); - - if (exception_) - std::rethrow_exception(exception_); - return result_.value(); - } - -private: - // HACK: Not all desired types are default constructable. But we need the - // entire struct to be constructed for awaiting. std::optional takes care of - // that. - optional result_; - std::exception_ptr exception_{ nullptr }; - -protected: - void setException(const std::exception_ptr &e) { - exception_ = e; - } - void setValue(const T &v) { - result_.emplace(v); - } - void setValue(T &&v) { - result_.emplace(std::move(v)); - } -}; - -template <> -struct CallbackAwaiter { - bool await_ready() noexcept { - return false; - } - - void await_resume() noexcept(false) { - if (exception_) - std::rethrow_exception(exception_); - } - -private: - std::exception_ptr exception_{ nullptr }; - -protected: - void setException(const std::exception_ptr &e) { - exception_ = e; - } -}; - -// An ok implementation of sync_await. This allows one to call -// coroutines and wait for the result from a function. -template -auto sync_wait(Await &&await) { - static_assert(is_awaitable_v >); - using value_type = typename await_result::type; - std::condition_variable cv; - std::mutex mtx; - std::atomic flag = false; - std::exception_ptr exception_ptr; - std::unique_lock lk(mtx); - - if constexpr (std::is_same_v) { - auto task = [&]() -> AsyncTask { - try { - co_await await; - } catch (...) { - exception_ptr = std::current_exception(); - } - std::unique_lock lk(mtx); - flag = true; - cv.notify_all(); - }; - - std::thread thr([&]() { task(); }); - cv.wait(lk, [&]() { return (bool)flag; }); - thr.join(); - if (exception_ptr) - std::rethrow_exception(exception_ptr); - } else { - optional value; - auto task = [&]() -> AsyncTask { - try { - value = co_await await; - } catch (...) { - exception_ptr = std::current_exception(); - } - std::unique_lock lk(mtx); - flag = true; - cv.notify_all(); - }; - - std::thread thr([&]() { task(); }); - cv.wait(lk, [&]() { return (bool)flag; }); - assert(value.has_value() == true || exception_ptr); - thr.join(); - - if (exception_ptr) - std::rethrow_exception(exception_ptr); - - return std::move(value.value()); - } -} - -// Converts a task (or task like) promise into std::future for old-style async -template -inline auto co_future(Await &&await) noexcept - -> std::future > { - using Result = await_result_t; - std::promise prom; - auto fut = prom.get_future(); - [](std::promise prom, Await await) -> AsyncTask { - try { - if constexpr (std::is_void_v) { - co_await std::move(await); - prom.set_value(); - } else - prom.set_value(co_await std::move(await)); - } catch (...) { - prom.set_exception(std::current_exception()); - } - }(std::move(prom), std::move(await)); - return fut; -} - -namespace internal { -struct TimerAwaiter : CallbackAwaiter { - TimerAwaiter(trantor::EventLoop *loop, - const std::chrono::duration &delay) : - loop_(loop), delay_(delay.count()) { - } - TimerAwaiter(trantor::EventLoop *loop, double delay) : - loop_(loop), delay_(delay) { - } - void await_suspend(std::coroutine_handle<> handle) { - loop_->runAfter(delay_, [handle]() { handle.resume(); }); - } - -private: - trantor::EventLoop *loop_; - double delay_; -}; -} // namespace internal - -inline internal::TimerAwaiter sleepCoro( - trantor::EventLoop *loop, - const std::chrono::duration &delay) noexcept { - assert(loop); - return internal::TimerAwaiter(loop, delay); -} - -inline internal::TimerAwaiter sleepCoro(trantor::EventLoop *loop, - double delay) noexcept { - assert(loop); - return internal::TimerAwaiter(loop, delay); -} - -template > -struct is_resumable : std::false_type { -}; - -template -struct is_resumable< - T, - std::void_t()))> > - : std::true_type { -}; - -template <> -struct is_resumable > : std::true_type { -}; - -template -constexpr bool is_resumable_v = is_resumable::value; - -} // namespace drogon diff --git a/modules/drogon/drogon/lib/tests/unittests/CoroutineTest.cc b/modules/drogon/drogon/lib/tests/unittests/CoroutineTest.cc deleted file mode 100644 index e14cb5b..0000000 --- a/modules/drogon/drogon/lib/tests/unittests/CoroutineTest.cc +++ /dev/null @@ -1,133 +0,0 @@ -#include -#include - -using namespace drogon; - -namespace drogon::internal { -struct SomeStruct { - ~SomeStruct() { - beenDestructed = true; - } - static bool beenDestructed; -}; - -bool SomeStruct::beenDestructed = false; - -struct StructAwaiter : public CallbackAwaiter > { - void await_suspend(std::coroutine_handle<> handle) { - setValue(std::make_shared()); - handle.resume(); - } -}; - -} // namespace drogon::internal - -// Workarround limitation of macros -template -using is_int = std::is_same; -template -using is_void = std::is_same; - -DROGON_TEST(CroutineBasics) { - // Basic checks making sure coroutine works as expected - STATIC_REQUIRE(is_awaitable_v >); - STATIC_REQUIRE(is_awaitable_v >); - STATIC_REQUIRE(is_awaitable_v >); - STATIC_REQUIRE(is_awaitable_v >); - STATIC_REQUIRE(is_int > >::value); - STATIC_REQUIRE(is_void > >::value); - - // No, you cannot await AsyncTask. By design - STATIC_REQUIRE(is_awaitable_v == false); - - // AsyncTask should execute eagerly - int m = 0; - [&m]() -> AsyncTask { - m = 1; - co_return; - }(); - REQUIRE(m == 1); - - // Make sure sync_wait works - CHECK(sync_wait([]() -> Task { co_return 1; }()) == 1); - - // make sure it does affect the outside world - int n = 0; - sync_wait([&]() -> Task<> { - n = 1; - co_return; - }()); - CHECK(n == 1); - - // Testing that exceptions can propergate through coroutines - auto throw_in_task = [TEST_CTX]() -> Task<> { - auto f = []() -> Task<> { throw std::runtime_error("test error"); }; - - CHECK_THROWS_AS(co_await f(), std::runtime_error); - }; - sync_wait(throw_in_task()); - - // Test sync_wait propergrates exception - auto throws = []() -> Task<> { - throw std::runtime_error("bla"); - co_return; - }; - CHECK_THROWS_AS(sync_wait(throws()), std::runtime_error); - - // Test co_return non-copyable object works - auto return_unique_ptr = [TEST_CTX]() -> Task > { - co_return std::make_unique(42); - }; - CHECK(*sync_wait(return_unique_ptr()) == 42); - - // Test co_awaiting non-copyable object works - auto await_non_copyable = [TEST_CTX]() -> Task<> { - auto return_unique_ptr = []() -> Task > { - co_return std::make_unique(123); - }; - auto ptr = co_await return_unique_ptr(); - CHECK(*ptr == 123); - }; - sync_wait(await_non_copyable()); -} - -DROGON_TEST(CompilcatedCoroutineLifetime) { - auto coro = []() -> Task > { - auto coro2 = []() -> Task { - auto coro3 = []() -> Task { - co_return std::string("Hello, World!"); - }; - auto coro4 = [coro3 = std::move(coro3)]() -> Task { - auto coro5 = []() -> Task<> { co_return; }; - co_await coro5(); - co_return co_await coro3(); - }; - co_return co_await coro4(); - }; - - co_return coro2(); - }; - - auto task1 = coro(); - auto task2 = sync_wait(task1); - std::string str = sync_wait(task2); - - CHECK(str == "Hello, World!"); -} - -DROGON_TEST(CoroutineDestruction) { - // Test coroutine destruction - auto destruct = []() -> Task<> { - auto awaitStruct = []() -> Task > { - co_return co_await internal::StructAwaiter(); - }; - - auto awaitNothing = [awaitStruct]() -> Task<> { - co_await awaitStruct(); - }; - - co_await awaitNothing(); - }; - sync_wait(destruct()); - CHECK(internal::SomeStruct::beenDestructed == true); -} \ No newline at end of file