// Copyright (c) 2022 Klemens D. Morgenstern // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) #ifndef BOOST_COBALT_HANDLER_HPP #define BOOST_COBALT_HANDLER_HPP #include #include #include #include #include #include #include #include #include namespace boost::cobalt { namespace detail { enum class completed_immediately_t { no, maybe, yes, initiating }; struct completion_handler_noop_executor { executor exec; completed_immediately_t * completed_immediately = nullptr; template void execute(Fn && fn) const { // only allow it when we're still initializing if (completed_immediately && ((*completed_immediately == completed_immediately_t::initiating) || (*completed_immediately == completed_immediately_t::maybe))) { // only use this indicator if the fn will actually call our completion-handler // otherwise this was a single op in a composed operation *completed_immediately = completed_immediately_t::maybe; fn(); // yes means completion_handler::operator() was called, so we're good. if (*completed_immediately != completed_immediately_t::yes) *completed_immediately = completed_immediately_t::initiating; } else { asio::post(exec, std::forward(fn)); } } friend bool operator==(const completion_handler_noop_executor&, const completion_handler_noop_executor&) noexcept { return true; } friend bool operator!=(const completion_handler_noop_executor&, const completion_handler_noop_executor&) noexcept { return false; } completion_handler_noop_executor(const completion_handler_noop_executor & rhs) noexcept = default; completion_handler_noop_executor(cobalt::executor inner, completed_immediately_t * completed_immediately) : exec(std::move(inner)), completed_immediately(completed_immediately) { } }; struct completion_handler_base { using cancellation_slot_type = asio::cancellation_slot; cancellation_slot_type cancellation_slot ; cancellation_slot_type get_cancellation_slot() const noexcept { return cancellation_slot ; } using executor_type = executor; const executor_type & executor_ ; const executor_type & get_executor() const noexcept { return executor_ ; } #if !defined(BOOST_COBALT_NO_PMR) using allocator_type = pmr::polymorphic_allocator; pmr::polymorphic_allocator allocator ; allocator_type get_allocator() const noexcept { return allocator ; } #else using allocator_type = detail::sbo_allocator; detail::sbo_allocator allocator ; allocator_type get_allocator() const noexcept { return allocator ; } #endif using immediate_executor_type = completion_handler_noop_executor; completed_immediately_t * completed_immediately = nullptr; immediate_executor_type get_immediate_executor() const noexcept { return {get_executor(), completed_immediately}; } template requires (requires (Promise p) {{p.get_executor()} -> std::same_as;}) completion_handler_base(std::coroutine_handle h, completed_immediately_t * completed_immediately = nullptr) : cancellation_slot(asio::get_associated_cancellation_slot(h.promise())), executor_(h.promise().get_executor()), #if !defined(BOOST_COBALT_NO_PMR) allocator(asio::get_associated_allocator(h.promise(), this_thread::get_allocator())), #else allocator(detail::get_null_sbo_resource()), #endif completed_immediately(completed_immediately) { } #if !defined(BOOST_COBALT_NO_PMR) template requires (requires (Promise p) {{p.get_executor()} -> std::same_as;}) completion_handler_base(std::coroutine_handle h, pmr::memory_resource * resource, completed_immediately_t * completed_immediately = nullptr) : cancellation_slot(asio::get_associated_cancellation_slot(h.promise())), executor_(h.promise().get_executor()), allocator(resource), completed_immediately(completed_immediately) { } #else template requires (requires (Promise p) {{p.get_executor()} -> std::same_as;}) completion_handler_base(std::coroutine_handle h, detail::sbo_resource * resource, completed_immediately_t * completed_immediately = nullptr) : cancellation_slot(asio::get_associated_cancellation_slot(h.promise())), executor_(h.promise().get_executor()), allocator(resource), completed_immediately(completed_immediately) { } #endif }; template void assign_cancellation(std::coroutine_handle, Handler &&) {} template void assign_cancellation(std::coroutine_handle h, Handler && func) { if constexpr (requires {h.promise().get_cancellation_slot();}) if (h.promise().get_cancellation_slot().is_connected()) h.promise().get_cancellation_slot().assign(std::forward(func)); } template const executor & get_executor(std::coroutine_handle h) { if constexpr (requires {h.promise().get_executor();}) { static_assert(std::same_as, "for performance reasons, the get_executor function on a promise must return a const reference"); return h.promise().get_executor(); } else return this_thread::get_executor(); } inline const executor & get_executor(std::coroutine_handle<>) { return this_thread::get_executor(); } } template struct handler { void operator()(Args ... args) { result.emplace(static_cast(args)...); } handler(std::optional> &result) : result(result) {} private: std::optional> &result; }; template handler(std::optional> &result) -> handler; template struct completion_handler : detail::completion_handler_base { completion_handler(completion_handler && ) = default; template completion_handler(std::coroutine_handle h, std::optional> &result, detail::completed_immediately_t * completed_immediately = nullptr #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) , const boost::source_location & loc = BOOST_CURRENT_LOCATION #endif ) : completion_handler_base(h, completed_immediately), self(h.address()), result(result) #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) , loc_(loc) #endif { } #if !defined(BOOST_COBALT_NO_PMR) template completion_handler(std::coroutine_handle h, std::optional> &result, pmr::memory_resource * resource, detail::completed_immediately_t * completed_immediately = nullptr #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) , const boost::source_location & loc = BOOST_CURRENT_LOCATION #endif ) : completion_handler_base(h, resource, completed_immediately), self(h.address()), result(result) #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) , loc_(loc) #endif { } #else template completion_handler(std::coroutine_handle h, std::optional> &result, detail::sbo_resource * resource, detail::completed_immediately_t * completed_immediately = nullptr) : completion_handler_base(h, resource, completed_immediately), self(h.address()), result(result) { } #endif void operator()(Args ... args) { #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) BOOST_ASIO_HANDLER_LOCATION((loc_.file_name(), loc_.line(), loc_.function_name())); #endif result.emplace(std::move(args)...); BOOST_ASSERT(this->self != nullptr); auto p = this->self.release(); if (completed_immediately != nullptr && *completed_immediately == detail::completed_immediately_t::maybe) { *completed_immediately = detail::completed_immediately_t::yes; return; } std::move(p)(); } using result_type = std::optional>; ~completion_handler() { if (self && completed_immediately && *completed_immediately == detail::completed_immediately_t::initiating && std::uncaught_exceptions() > 0) self.release(); } private: unique_handle self; std::optional> &result; #if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING) boost::source_location loc_; #endif }; }; #endif //BOOST_COBALT_HANDLER_HPP