/////////////////////////////////////////////////////////////////////////////// // Copyright (c) Lewis Baker // Licenced under MIT license. See LICENSE.txt for details. /////////////////////////////////////////////////////////////////////////////// #ifndef CPPCORO_TASK_HPP_INCLUDED #define CPPCORO_TASK_HPP_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include namespace cppcoro { template class task; namespace detail { class task_promise_base { friend struct final_awaitable; struct final_awaitable { bool await_ready() const noexcept { return false; } #if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER template cppcoro::coroutine_handle<> await_suspend( cppcoro::coroutine_handle coro) noexcept { return coro.promise().m_continuation; } #else // HACK: Need to add CPPCORO_NOINLINE to await_suspend() method // to avoid MSVC 2017.8 from spilling some local variables in // await_suspend() onto the coroutine frame in some cases. // Without this, some tests in async_auto_reset_event_tests.cpp // were crashing under x86 optimised builds. template CPPCORO_NOINLINE void await_suspend(cppcoro::coroutine_handle coroutine) noexcept { task_promise_base& promise = coroutine.promise(); // Use 'release' memory semantics in case we finish before the // awaiter can suspend so that the awaiting thread sees our // writes to the resulting value. // Use 'acquire' memory semantics in case the caller registered // the continuation before we finished. Ensure we see their write // to m_continuation. if (promise.m_state.exchange(true, std::memory_order_acq_rel)) { promise.m_continuation.resume(); } } #endif void await_resume() noexcept {} }; public: task_promise_base() noexcept #if !CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER : m_state(false) #endif {} auto initial_suspend() noexcept { return cppcoro::suspend_always{}; } auto final_suspend() noexcept { return final_awaitable{}; } #if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER void set_continuation(cppcoro::coroutine_handle<> continuation) noexcept { m_continuation = continuation; } #else bool try_set_continuation(cppcoro::coroutine_handle<> continuation) { m_continuation = continuation; return !m_state.exchange(true, std::memory_order_acq_rel); } #endif private: cppcoro::coroutine_handle<> m_continuation; #if !CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER // Initially false. Set to true when either a continuation is registered // or when the coroutine has run to completion. Whichever operation // successfully transitions from false->true got there first. std::atomic m_state; #endif }; template class task_promise final : public task_promise_base { public: task_promise() noexcept {} ~task_promise() { switch (m_resultType) { case result_type::value: m_value.~T(); break; case result_type::exception: m_exception.~exception_ptr(); break; default: break; } } task get_return_object() noexcept; void unhandled_exception() noexcept { ::new (static_cast(std::addressof(m_exception))) std::exception_ptr( std::current_exception()); m_resultType = result_type::exception; } template< typename VALUE, typename = std::enable_if_t>> void return_value(VALUE&& value) noexcept(std::is_nothrow_constructible_v) { ::new (static_cast(std::addressof(m_value))) T(std::forward(value)); m_resultType = result_type::value; } T& result() & { if (m_resultType == result_type::exception) { std::rethrow_exception(m_exception); } assert(m_resultType == result_type::value); return m_value; } // HACK: Need to have co_await of task return prvalue rather than // rvalue-reference to work around an issue with MSVC where returning // rvalue reference of a fundamental type from await_resume() will // cause the value to be copied to a temporary. This breaks the // sync_wait() implementation. // See https://github.com/lewissbaker/cppcoro/issues/40#issuecomment-326864107 using rvalue_type = std::conditional_t< std::is_arithmetic_v || std::is_pointer_v, T, T&&>; rvalue_type result() && { if (m_resultType == result_type::exception) { std::rethrow_exception(m_exception); } assert(m_resultType == result_type::value); return std::move(m_value); } private: enum class result_type { empty, value, exception }; result_type m_resultType = result_type::empty; union { T m_value; std::exception_ptr m_exception; }; }; template<> class task_promise : public task_promise_base { public: task_promise() noexcept = default; task get_return_object() noexcept; void return_void() noexcept {} void unhandled_exception() noexcept { m_exception = std::current_exception(); } void result() { if (m_exception) { std::rethrow_exception(m_exception); } } private: std::exception_ptr m_exception; }; template class task_promise : public task_promise_base { public: task_promise() noexcept = default; task get_return_object() noexcept; void unhandled_exception() noexcept { m_exception = std::current_exception(); } void return_value(T& value) noexcept { m_value = std::addressof(value); } T& result() { if (m_exception) { std::rethrow_exception(m_exception); } return *m_value; } private: T* m_value = nullptr; std::exception_ptr m_exception; }; } /// \brief /// A task represents an operation that produces a result both lazily /// and asynchronously. /// /// When you call a coroutine that returns a task, the coroutine /// simply captures any passed parameters and returns exeuction to the /// caller. Execution of the coroutine body does not start until the /// coroutine is first co_await'ed. template class [[nodiscard]] task { public: using promise_type = detail::task_promise; using value_type = T; private: struct awaitable_base { cppcoro::coroutine_handle m_coroutine; awaitable_base(cppcoro::coroutine_handle coroutine) noexcept : m_coroutine(coroutine) {} bool await_ready() const noexcept { return !m_coroutine || m_coroutine.done(); } #if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER cppcoro::coroutine_handle<> await_suspend( cppcoro::coroutine_handle<> awaitingCoroutine) noexcept { m_coroutine.promise().set_continuation(awaitingCoroutine); return m_coroutine; } #else bool await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept { // NOTE: We are using the bool-returning version of await_suspend() here // to work around a potential stack-overflow issue if a coroutine // awaits many synchronously-completing tasks in a loop. // // We first start the task by calling resume() and then conditionally // attach the continuation if it has not already completed. This allows us // to immediately resume the awaiting coroutine without increasing // the stack depth, avoiding the stack-overflow problem. However, it has // the down-side of requiring a std::atomic to arbitrate the race between // the coroutine potentially completing on another thread concurrently // with registering the continuation on this thread. // // We can eliminate the use of the std::atomic once we have access to // coroutine_handle-returning await_suspend() on both MSVC and Clang // as this will provide ability to suspend the awaiting coroutine and // resume another coroutine with a guaranteed tail-call to resume(). m_coroutine.resume(); return m_coroutine.promise().try_set_continuation(awaitingCoroutine); } #endif }; public: task() noexcept : m_coroutine(nullptr) {} explicit task(cppcoro::coroutine_handle coroutine) : m_coroutine(coroutine) {} task(task&& t) noexcept : m_coroutine(t.m_coroutine) { t.m_coroutine = nullptr; } /// Disable copy construction/assignment. task(const task&) = delete; task& operator=(const task&) = delete; /// Frees resources used by this task. ~task() { if (m_coroutine) { m_coroutine.destroy(); } } task& operator=(task&& other) noexcept { if (std::addressof(other) != this) { if (m_coroutine) { m_coroutine.destroy(); } m_coroutine = other.m_coroutine; other.m_coroutine = nullptr; } return *this; } /// \brief /// Query if the task result is complete. /// /// Awaiting a task that is ready is guaranteed not to block/suspend. bool is_ready() const noexcept { return !m_coroutine || m_coroutine.done(); } auto operator co_await() const & noexcept { struct awaitable : awaitable_base { using awaitable_base::awaitable_base; decltype(auto) await_resume() { if (!this->m_coroutine) { throw broken_promise{}; } return this->m_coroutine.promise().result(); } }; return awaitable{ m_coroutine }; } auto operator co_await() const && noexcept { struct awaitable : awaitable_base { using awaitable_base::awaitable_base; decltype(auto) await_resume() { if (!this->m_coroutine) { throw broken_promise{}; } return std::move(this->m_coroutine.promise()).result(); } }; return awaitable{ m_coroutine }; } /// \brief /// Returns an awaitable that will await completion of the task without /// attempting to retrieve the result. auto when_ready() const noexcept { struct awaitable : awaitable_base { using awaitable_base::awaitable_base; void await_resume() const noexcept {} }; return awaitable{ m_coroutine }; } private: cppcoro::coroutine_handle m_coroutine; }; namespace detail { template task task_promise::get_return_object() noexcept { return task{ cppcoro::coroutine_handle::from_promise(*this) }; } inline task task_promise::get_return_object() noexcept { return task{ cppcoro::coroutine_handle::from_promise(*this) }; } template task task_promise::get_return_object() noexcept { return task{ cppcoro::coroutine_handle::from_promise(*this) }; } } template auto make_task(AWAITABLE awaitable) -> task::await_result_t>> { co_return co_await static_cast(awaitable); } } #endif