/************************************************************************************ * * D++, A Lightweight C++ library for Discord * * Copyright 2022 Craig Edwards and D++ contributors * (https://github.com/brainboxdotcc/DPP/graphs/contributors) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ************************************************************************************/ #pragma once #include #include namespace dpp { struct awaitable_dummy { int *promise_dummy = nullptr; }; } #ifdef DPP_CORO #include // Do not include as coro.h includes or depending on clang version #include #include #include #include #include #include namespace dpp { namespace detail::promise { /** * @brief State of a promise */ enum state_flags { /** * @brief Promise is empty */ sf_none = 0b0000000, /** * @brief Promise has spawned an awaitable */ sf_has_awaitable = 0b00000001, /** * @brief Promise is being awaited */ sf_awaited = 0b00000010, /** * @brief Promise has a result */ sf_ready = 0b00000100, /** * @brief Promise has completed, no more results are expected */ sf_done = 0b00001000, /** * @brief Promise was broken - future or promise is gone */ sf_broken = 0b0010000 }; template class promise_base; /** * @brief Empty result from void-returning awaitable */ struct empty{}; /** * @brief Variant for the 3 conceptual values of a coroutine: */ template using result_t = std::variant, empty, T>, std::exception_ptr>; template void spawn_sync_wait_job(auto* awaitable, std::condition_variable &cv, auto&& result); } /* namespace detail::promise */ template class basic_awaitable { protected: /** * @brief Implementation for sync_wait. This is code used by sync_wait, sync_wait_for, sync_wait_until. * * @tparam Timed Whether the wait function times out or not * @param do_wait Function to do the actual wait on the cv * @return If T is void, returns a boolean for which true means the awaitable completed, false means it timed out. * @return If T is non-void, returns a std::optional for which an absence of value means timed out. */ template auto sync_wait_impl(auto&& do_wait) { using result_type = decltype(detail::co_await_resolve(std::declval()).await_resume()); using variant_type = detail::promise::result_t; variant_type result; std::condition_variable cv; detail::promise::spawn_sync_wait_job(static_cast(this), cv, result); do_wait(cv, result); /* * Note: we use .index() here to support dpp::promise & dpp::promise :D */ if (result.index() == 2) { std::rethrow_exception(std::get<2>(result)); } if constexpr (!Timed) { // no timeout if constexpr (!std::is_void_v) { return std::get<1>(result); } } else { // timeout if constexpr (std::is_void_v) { return result.index() == 1 ? true : false; } else { return result.index() == 1 ? std::optional{std::get<1>(result)} : std::nullopt; } } } public: /** * @brief Blocks this thread and waits for the awaitable to finish. * * @attention This will BLOCK THE THREAD. It is likely you want to use co_await instead. * @return If T is void, returns a boolean for which true means the awaitable completed, false means it timed out. * @return If T is non-void, returns a std::optional for which an absence of value means timed out. */ auto sync_wait() { return sync_wait_impl([](std::condition_variable &cv, auto&& result) { std::mutex m{}; std::unique_lock lock{m}; cv.wait(lock, [&result] { return result.index() != 0; }); }); } /** * @brief Blocks this thread and waits for the awaitable to finish. * * @attention This will BLOCK THE THREAD. It is likely you want to use co_await instead. * @param duration Maximum duration to wait for * @return If T is void, returns a boolean for which true means the awaitable completed, false means it timed out. * @return If T is non-void, returns a std::optional for which an absence of value means timed out. */ template auto sync_wait_for(const std::chrono::duration& duration) { return sync_wait_impl([duration](std::condition_variable &cv, auto&& result) { std::mutex m{}; std::unique_lock lock{m}; cv.wait_for(lock, duration, [&result] { return result.index() != 0; }); }); } /** * @brief Blocks this thread and waits for the awaitable to finish. * * @attention This will BLOCK THE THREAD. It is likely you want to use co_await instead. * @param time Maximum time point to wait for * @return If T is void, returns a boolean for which true means the awaitable completed, false means it timed out. * @return If T is non-void, returns a std::optional for which an absence of value means timed out. */ template auto sync_wait_until(const std::chrono::time_point &time) { return sync_wait_impl([time](std::condition_variable &cv, auto&& result) { std::mutex m{}; std::unique_lock lock{m}; cv.wait_until(lock, time, [&result] { return result.index() != 0; }); }); } }; /** * @brief Generic awaitable class, represents a future value that can be co_await-ed on. * * Roughly equivalent of std::future for coroutines, with the crucial distinction that the future does not own a reference to a "shared state". * It holds a non-owning reference to the promise, which must be kept alive for the entire lifetime of the awaitable. * * @tparam T Type of the asynchronous value * @see promise */ template class awaitable : public basic_awaitable> { protected: friend class detail::promise::promise_base; using shared_state = detail::promise::promise_base; using state_flags = detail::promise::state_flags; /** * @brief The type of the result produced by this task. */ using result_type = T; /** * @brief Non-owning pointer to the promise, which must be kept alive for the entire lifetime of the awaitable. */ shared_state *state_ptr = nullptr; /** * @brief Construct from a promise. * * @param promise The promise to refer to. */ awaitable(shared_state *promise) noexcept : state_ptr{promise} {} /** * @brief Abandons the promise. * * Set the promise's state to broken and unlinks this awaitable. * * @return uint8_t Flags previously held before setting them to broken */ uint8_t abandon(); /** * @brief Awaiter returned by co_await. * * Contains the await_ready, await_suspend and await_resume functions required by the C++ standard. * This class is CRTP-like, in that it will refer to an object derived from awaitable. * * @tparam Derived Type of reference to refer to the awaitable. */ template struct awaiter { Derived awaitable_obj; /** * @brief First function called by the standard library when co_await-ing this object. * * @throws dpp::logic_exception If the awaitable's valid() would return false. * @return bool Whether the result is ready, in which case we don't need to suspend */ bool await_ready() const; /** * @brief Second function called by the standard library when co_await-ing this object. * * @throws dpp::logic_exception If the awaitable's valid() would return false. * At this point the coroutine frame was allocated and suspended. * * @return bool Whether we do need to suspend or not */ bool await_suspend(detail::std_coroutine::coroutine_handle<> handle); /** * @brief Third and final function called by the standard library when co_await-ing this object, after resuming. * * @throw ? Any exception that occured during the retrieval of the value will be thrown * @return T The result. */ T await_resume(); }; public: /** * @brief Construct an empty awaitable. * * Such an awaitable must be assigned a promise before it can be awaited. */ awaitable() = default; /** * @brief Copy construction is disabled. */ awaitable(const awaitable&) = delete; /** * @brief Move from another awaitable. * * @param rhs The awaitable to move from, left in an unspecified state after this. */ awaitable(awaitable&& rhs) noexcept : state_ptr(std::exchange(rhs.state_ptr, nullptr)) { } /** * @brief Title :) * * We use this in the destructor */ void if_this_causes_an_invalid_read_your_promise_was_destroyed_before_your_awaitable____check_your_promise_lifetime() { abandon(); } /** * @brief Destructor. * * May signal to the promise that it was destroyed. */ ~awaitable(); /** * @brief Copy assignment is disabled. */ awaitable& operator=(const awaitable&) = delete; /** * @brief Move from another awaitable. * * @param rhs The awaitable to move from, left in an unspecified state after this. * @return *this */ awaitable& operator=(awaitable&& rhs) noexcept { abandon(); state_ptr = std::exchange(rhs.state_ptr, nullptr); return *this; } /** * @brief Check whether this awaitable refers to a valid promise. * * @return bool Whether this awaitable refers to a valid promise or not */ bool valid() const noexcept; /** * @brief Check whether or not co_await-ing this would suspend the caller, i.e. if we have the result or not * * @return bool Whether we already have the result or not */ bool await_ready() const; /** * @brief Overload of the co_await operator. * * @return Returns an @ref awaiter referencing this awaitable. */ template requires (std::is_base_of_v>) friend awaiter operator co_await(Derived& obj) noexcept { return {obj}; } /** * @brief Overload of the co_await operator. Returns an @ref awaiter referencing this awaitable. * * @return Returns an @ref awaiter referencing this awaitable. */ template requires (std::is_base_of_v>) friend awaiter operator co_await(Derived&& obj) noexcept { return {std::move(obj)}; } }; namespace detail::promise { /** * @brief Base class defining logic common to all promise types, aka the "write" end of an awaitable. */ template class promise_base { protected: friend class awaitable; /** * @brief Variant representing one of either 3 states of the result value : empty, result, exception. */ using storage_type = result_t; /** * @brief State of the result value. * * @see storage_type * * @note use .index() instead of std::holds_alternative to support promise_base and promise_base :) */ storage_type value = std::monostate{}; /** * @brief State of the awaitable tied to this promise. */ std::atomic state = sf_none; /** * @brief Coroutine handle currently awaiting the completion of this promise. */ std_coroutine::coroutine_handle<> awaiter = nullptr; /** * @brief Check if the result is empty, throws otherwise. * * @throw dpp::logic_exception if the result isn't empty. */ void throw_if_not_empty() { if (value.index() != 0) [[unlikely]] { throw dpp::logic_exception("cannot set a value on a promise that already has one"); } } /** * @brief Unlinks this promise from its currently linked awaiter and returns it. * * At the time of writing this is only used in the case of a serious internal error in dpp::task. * Avoid using this as this will crash if the promise is used after this. */ std_coroutine::coroutine_handle<> release_awaiter() { return std::exchange(awaiter, nullptr); } /** * @brief Construct a new promise, with empty result. */ promise_base() = default; /** * @brief Copy construction is disabled. */ promise_base(const promise_base&) = delete; /** * @brief Move construction is disabled. * * awaitable hold a pointer to this object so moving is not possible. */ promise_base(promise_base&& rhs) = delete; public: /** * @brief Copy assignment is disabled. */ promise_base &operator=(const promise_base&) = delete; /** * @brief Move assignment is disabled. */ promise_base &operator=(promise_base&& rhs) = delete; /** * @brief Set this promise to an exception and resume any awaiter. * * @tparam Notify Whether to resume any awaiter or not. * @throws dpp::logic_exception if the promise is not empty. * @throws ? Any exception thrown by the coroutine if resumed will propagate */ template void set_exception(std::exception_ptr ptr) { throw_if_not_empty(); value.template emplace<2>(std::move(ptr)); [[maybe_unused]] auto previous_value = this->state.fetch_or(sf_ready, std::memory_order::acq_rel); if constexpr (Notify) { if ((previous_value & sf_awaited) != 0) { this->awaiter.resume(); } } } /** * @brief Notify a currently awaiting coroutine that the result is ready. * * @note This may resume the coroutine on the current thread. * @throws ? Any exception thrown by the coroutine if resumed will propagate */ void notify_awaiter() { if ((state.load(std::memory_order::acquire) & sf_awaited) != 0) { awaiter.resume(); } } /** * @brief Get an awaitable object for this promise. * * @throws dpp::logic_exception if get_awaitable has already been called on this object. * @return awaitable An object that can be co_await-ed to retrieve the value of this promise. */ awaitable get_awaitable() { uint8_t previous_flags = state.fetch_or(sf_has_awaitable, std::memory_order::relaxed); if (previous_flags & sf_has_awaitable) [[unlikely]] { throw dpp::logic_exception{"an awaitable was already created from this promise"}; } return {this}; } }; } /** * @brief Generic promise class, represents the owning potion of an asynchronous value. * * This class is roughly equivalent to std::promise, with the crucial distinction that the promise *IS* the shared state. * As such, the promise needs to be kept alive for the entire time a value can be retrieved. * * @tparam T Type of the asynchronous value * @see awaitable */ template class basic_promise : public detail::promise::promise_base { public: using detail::promise::promise_base::promise_base; using detail::promise::promise_base::operator=; /** * @brief Construct the result in place by forwarding the arguments, and by default resume any awaiter. * * @tparam Notify Whether to resume any awaiter or not. * @throws dpp::logic_exception if the promise is not empty. */ template requires (std::constructible_from) void emplace_value(Args&&... args) { this->throw_if_not_empty(); try { this->value.template emplace<1>(std::forward(args)...); } catch (...) { this->value.template emplace<2>(std::current_exception()); } [[maybe_unused]] auto previous_value = this->state.fetch_or(detail::promise::sf_ready, std::memory_order::acq_rel); if constexpr (Notify) { if (previous_value & detail::promise::sf_awaited) { this->awaiter.resume(); } } } /** * @brief Construct the result by forwarding reference, and resume any awaiter. * * @tparam Notify Whether to resume any awaiter or not. * @throws dpp::logic_exception if the promise is not empty. */ template requires (std::convertible_to) void set_value(U&& v) { emplace_value(std::forward(v)); } /** * @brief Construct a void result, and resume any awaiter. * * @tparam Notify Whether to resume any awaiter or not. * @throws dpp::logic_exception if the promise is not empty. */ template requires (std::is_void_v) void set_value() { this->throw_if_not_empty(); this->value.template emplace<1>(); [[maybe_unused]] auto previous_value = this->state.fetch_or(detail::promise::sf_ready, std::memory_order::acq_rel); if constexpr (Notify) { if (previous_value & detail::promise::sf_awaited) { this->awaiter.resume(); } } } }; /** * @brief Generic promise class, represents the owning potion of an asynchronous value. * * This class is roughly equivalent to std::promise, with the crucial distinction that the promise *IS* the shared state. * As such, the promise needs to be kept alive for the entire time a value can be retrieved. * * The difference between basic_promise and this object is that this one is moveable as it wraps an underlying basic_promise in a std::unique_ptr. * * @see awaitable */ template class moveable_promise { /** * @brief Shared state, wrapped in a unique_ptr to allow move without disturbing an awaitable's promise pointer. */ std::unique_ptr> shared_state = std::make_unique>(); public: /** * @copydoc basic_promise::emplace_value */ template requires (std::constructible_from) void emplace_value(Args&&... args) { shared_state->template emplace_value(std::forward(args)...); } /** * @copydoc basic_promise::set_value(U&&) */ template void set_value(U&& v) requires (std::convertible_to) { shared_state->template set_value(std::forward(v)); } /** * @copydoc basic_promise::set_value() */ template void set_value() requires (std::is_void_v) { shared_state->template set_value(); } /** * @copydoc basic_promise::set_value(T&&) */ template void set_exception(std::exception_ptr ptr) { shared_state->template set_exception(std::move(ptr)); } /** * @copydoc basic_promise::notify_awaiter */ void notify_awaiter() { shared_state->notify_awaiter(); } /** * @copydoc basic_promise::get_awaitable */ awaitable get_awaitable() { return shared_state->get_awaitable(); } }; template using promise = moveable_promise; template auto awaitable::abandon() -> uint8_t { uint8_t previous_state = state_flags::sf_broken; if (state_ptr) { previous_state = state_ptr->state.fetch_or(state_flags::sf_broken, std::memory_order::acq_rel); state_ptr = nullptr; } return previous_state; } template awaitable::~awaitable() { if_this_causes_an_invalid_read_your_promise_was_destroyed_before_your_awaitable____check_your_promise_lifetime(); } template bool awaitable::valid() const noexcept { return state_ptr != nullptr; } template bool awaitable::await_ready() const { if (!this->valid()) { throw dpp::logic_exception("cannot co_await an empty awaitable"); } uint8_t state = this->state_ptr->state.load(std::memory_order::relaxed); return state & detail::promise::sf_ready; } template template bool awaitable::awaiter::await_suspend(detail::std_coroutine::coroutine_handle<> handle) { auto &promise = *awaitable_obj.state_ptr; promise.awaiter = handle; auto previous_flags = promise.state.fetch_or(detail::promise::sf_awaited, std::memory_order::relaxed); if (previous_flags & detail::promise::sf_awaited) { throw dpp::logic_exception("awaitable is already being awaited"); } return !(previous_flags & detail::promise::sf_ready); } template template T awaitable::awaiter::await_resume() { auto &promise = *awaitable_obj.state_ptr; promise.state.fetch_and(~detail::promise::sf_awaited, std::memory_order::acq_rel); if (std::holds_alternative(promise.value)) { std::rethrow_exception(std::get<2>(promise.value)); } if constexpr (!std::is_void_v) { return std::get<1>(std::move(promise.value)); } else { return; } } template template bool awaitable::awaiter::await_ready() const { return static_cast(awaitable_obj).await_ready(); } } #include namespace dpp { namespace detail::promise { template void spawn_sync_wait_job(auto* awaitable, std::condition_variable &cv, auto&& result) { [](auto* awaitable_, std::condition_variable &cv_, auto&& result_) -> dpp::job { try { if constexpr (std::is_void_v) { co_await *awaitable_; result_.template emplace<1>(); } else { result_.template emplace<1>(co_await *awaitable_); } } catch (...) { result_.template emplace<2>(std::current_exception()); } cv_.notify_all(); }(awaitable, cv, std::forward(result)); } } } #endif /* DPP_CORO */