diff --git a/lib/inc/drogon/utils/coroutine.h b/lib/inc/drogon/utils/coroutine.h index d7eae9cd..509ca2aa 100644 --- a/lib/inc/drogon/utils/coroutine.h +++ b/lib/inc/drogon/utils/coroutine.h @@ -816,5 +816,55 @@ std::function async_func(Coro &&coro) async_run(std::move(coro)); }; } +namespace internal +{ +template +struct [[nodiscard]] EventLoopAwaiter : public drogon::CallbackAwaiter +{ + EventLoopAwaiter(std::function &&task, trantor::EventLoop *loop) + : task_(std::move(task)), loop_(loop) + { + } + void await_suspend(std::coroutine_handle<> handle) + { + loop_->queueInLoop([this, handle]() { + try + { + if constexpr (!std::is_same_v) + { + this->setValue(task_()); + handle.resume(); + } + else + { + task_(); + handle.resume(); + } + } + catch (const std::exception &err) + { + LOG_ERROR << err.what(); + this->setException(std::current_exception()); + handle.resume(); + } + }); + } + + private: + std::function task_; + trantor::EventLoop *loop_; +}; +} // namespace internal + +/** + * @brief Run a task in a given event loop and returns a resumable object that + * can be co_awaited in a coroutine. + */ +template +inline internal::EventLoopAwaiter queueInLoopCoro(trantor::EventLoop *loop, + std::function task) +{ + return internal::EventLoopAwaiter(std::move(task), loop); +} } // namespace drogon diff --git a/lib/tests/unittests/CoroutineTest.cc b/lib/tests/unittests/CoroutineTest.cc index 2b63a3f4..39fa0b32 100644 --- a/lib/tests/unittests/CoroutineTest.cc +++ b/lib/tests/unittests/CoroutineTest.cc @@ -107,6 +107,14 @@ DROGON_TEST(CroutineBasics) co_return; }); CHECK(testVar == 1); + async_run([TEST_CTX]() -> Task { + auto val = + co_await queueInLoopCoro(app().getLoop(), []() { return 42; }); + CHECK(val == 42); + }); + async_run([TEST_CTX]() -> Task { + co_await queueInLoopCoro(app().getLoop(), []() { LOG_DEBUG; }); + }); } DROGON_TEST(CompilcatedCoroutineLifetime)