include/unifex/at_coroutine_exit.hpp (209 lines of code) (raw):

/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License Version 2.0 with LLVM Exceptions * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * https://llvm.org/LICENSE.txt * * 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 <unifex/coroutine.hpp> #include <unifex/tag_invoke.hpp> #include <unifex/await_transform.hpp> #include <unifex/continuations.hpp> #include <unifex/stop_token_concepts.hpp> #include <unifex/unstoppable_token.hpp> #include <unifex/get_stop_token.hpp> #if UNIFEX_NO_COROUTINES # error "Coroutine support is required to use this header" #endif #include <tuple> #include <utility> #include <unifex/detail/prologue.hpp> namespace unifex { namespace _xchg_cont { inline constexpr struct _fn { template (typename ParentPromise, typename ChildPromise) (requires tag_invocable<_fn, ParentPromise&, continuation_handle<ChildPromise>>) UNIFEX_ALWAYS_INLINE continuation_handle<> operator()(ParentPromise& parent, continuation_handle<ChildPromise> action) const noexcept { return tag_invoke(*this, parent, (continuation_handle<ChildPromise>&&) action); } } exchange_continuation {}; } // _xchg_cont using _xchg_cont::exchange_continuation; struct _cleanup_promise_base { struct final_awaitable { bool await_ready() const noexcept { return false; } // Clang before clang-12 has a bug with coroutines that self-destruct in an // await_suspend that uses symmetric transfer. It appears that MSVC has the same // bug. So instead of symmetric transfer, we accept the stack growth and resume // the continuation from within await_suspend. #if (defined(__clang__) && (defined(__apple_build_version__) || __clang_major__ < 12)) || \ defined(_MSC_VER) template <typename CleanupPromise> // Apple-clang and clang-10 and prior need for await_suspend to be noinline. // MSVC and clang-11 can tolerate await_suspend to be inlined, so force it. #if defined(__apple_build_version__) || (defined(__clang__) && __clang_major__ <= 10) UNIFEX_NO_INLINE #else UNIFEX_ALWAYS_INLINE #endif void await_suspend(coro::coroutine_handle<CleanupPromise> h) const noexcept { auto continuation = h.promise().next(); h.destroy(); // The cleanup action has finished executing. Destroy it. continuation.resume(); } #else // No bugs here! OK to use symmetric transfer. template <typename CleanupPromise> coro::coroutine_handle<> await_suspend(coro::coroutine_handle<CleanupPromise> h) const noexcept { auto continuation = h.promise().next(); h.destroy(); // The cleanup action has finished executing. Destroy it. return continuation; } #endif void await_resume() const noexcept { } }; coro::suspend_always initial_suspend() noexcept { return {}; } final_awaitable final_suspend() noexcept { return {}; } [[noreturn]] void unhandled_exception() noexcept { UNIFEX_ASSERT(!"An exception happened in an async cleanup action. Calling terminate..."); std::terminate(); } void return_void() noexcept { } coro::coroutine_handle<> next() const noexcept { return isUnhandledDone_ ? continuation_.done() : continuation_.handle(); } template <typename Func> friend void tag_invoke(tag_t<visit_continuations>, const _cleanup_promise_base& p, Func&& func) { // Skip cleanup actions when visiting continuations: visit_continuations(p.continuation_, (Func &&) func); } friend unstoppable_token tag_invoke(tag_t<get_stop_token>, const _cleanup_promise_base&) noexcept { return unstoppable_token{}; } continuation_handle<> continuation_{}; bool isUnhandledDone_{false}; }; // The die_on_done algorithm implemented here could be implemented in terms of // let_done, but this implementation is simpler since it doesn't instantiate // a bunch of templates that will never be needed (no need to connect or start a // sender returned from the done transform function). template <typename Receiver> struct _die_on_done_rec { struct type { Receiver rec_; template (typename... Ts) (requires receiver_of<Receiver, Ts...>) void set_value(Ts&&... ts) && noexcept(is_nothrow_receiver_of_v<Receiver, Ts...>) { unifex::set_value((Receiver&&) rec_, (Ts&&) ts...); } template (typename E) (requires receiver<Receiver, E>) void set_error(E&& e) && noexcept { unifex::set_error((Receiver&&) rec_, (E&&) e); } [[noreturn]] void set_done() && noexcept { UNIFEX_ASSERT(!"A cleanup action tried to cancel. Calling terminate..."); std::terminate(); } }; }; template <typename Receiver> using _die_on_done_rec_t = typename _die_on_done_rec<remove_cvref_t<Receiver>>::type; template <typename Sender> struct _die_on_done { struct type { template < template <typename...> class Variant, template <typename...> class Tuple> using value_types = typename sender_traits<Sender>::template value_types<Variant, Tuple>; template <template <typename...> class Variant> using error_types = typename sender_traits<Sender>::template error_types<Variant>; static constexpr bool sends_done = false; template (typename Receiver) (requires sender_to<Sender, _die_on_done_rec_t<Receiver>>) auto connect(Receiver&& rec) && noexcept(is_nothrow_connectable_v<Sender, _die_on_done_rec_t<Receiver>>) -> connect_result_t<Sender, _die_on_done_rec_t<Receiver>> { return unifex::connect( (Sender&&) sender_, _die_on_done_rec_t<Receiver>{(Receiver&&) rec}); } Sender sender_; }; }; template <typename Sender> using _die_on_done_t = typename _die_on_done<remove_cvref_t<Sender>>::type; struct _die_on_done_fn { template (typename Value) (requires (!detail::_awaitable<Value>) AND sender<Value>) _die_on_done_t<Value> operator()(Value&& value) /*mutable*/ noexcept(std::is_nothrow_constructible_v<remove_cvref_t<Value>, Value>) { return _die_on_done_t<Value>{(Value&&) value}; } template <typename Value> Value&& operator()(Value&& value) const noexcept { return (Value&&) value; } }; template <typename... Ts> struct _cleanup_task; template <typename... Ts> struct _cleanup_promise : _cleanup_promise_base { template <typename Action> explicit _cleanup_promise(Action&&, Ts&... ts) noexcept : args_(ts...) {} _cleanup_task<Ts...> get_return_object() noexcept { return _cleanup_task<Ts...>( coro::coroutine_handle<_cleanup_promise>::from_promise(*this)); } coro::coroutine_handle<> unhandled_done() noexcept { // Record that we are processing an unhandled done signal. This is checked in // the final_suspend of the cleanup action to know which subsequent continuation // to resume. isUnhandledDone_ = true; // On unhandled_done, run the cleanup action: return coro::coroutine_handle<_cleanup_promise>::from_promise(*this); } template <typename Value> decltype(auto) await_transform(Value&& value) noexcept(noexcept( unifex::await_transform(*this, _die_on_done_fn{}((Value&&) value)))) { return unifex::await_transform(*this, _die_on_done_fn{}((Value&&) value)); } std::tuple<Ts&...> args_; }; template <typename... Ts> struct [[nodiscard]] _cleanup_task { using promise_type = _cleanup_promise<Ts...>; explicit _cleanup_task(coro::coroutine_handle<promise_type> coro) noexcept : continuation_(coro) {} _cleanup_task(_cleanup_task&& that) noexcept : continuation_(std::exchange(that.continuation_, {})) {} ~_cleanup_task() { UNIFEX_ASSERT(!continuation_); } bool await_ready() const noexcept { return false; } template <typename Promise> bool await_suspend(coro::coroutine_handle<Promise> parent) noexcept { continuation_.promise().continuation_ = exchange_continuation(parent.promise(), continuation_); return false; } std::tuple<Ts&...> await_resume() noexcept { return std::exchange(continuation_, {}).promise().args_; } private: continuation_handle<promise_type> continuation_; }; namespace _at_coroutine_exit { inline constexpr struct _fn { private: template <typename Action, typename... Ts> static _cleanup_task<Ts...> at_coroutine_exit(Action action, Ts... ts) { co_await std::move(action)(std::move(ts)...); } public: template (typename Action, typename... Ts) (requires callable<std::decay_t<Action>, std::decay_t<Ts>...>) _cleanup_task<Ts...> operator()(Action&& action, Ts&&... ts) const { return _fn::at_coroutine_exit((Action&&) action, (Ts&&) ts...); } } at_coroutine_exit{}; } // namespace _at_coroutine_exit using _at_coroutine_exit::at_coroutine_exit; } // namespace unifex #include <unifex/detail/epilogue.hpp>