5 // Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com)
7 // Distributed under the Boost Software License, Version 1.0. (See accompanying
8 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
11 #ifndef BOOST_ASIO_IMPL_AWAITABLE_HPP
12 #define BOOST_ASIO_IMPL_AWAITABLE_HPP
14 #if defined(_MSC_VER) && (_MSC_VER >= 1200)
16 #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
18 #include <boost/asio/detail/config.hpp>
22 #include <boost/asio/cancellation_signal.hpp>
23 #include <boost/asio/cancellation_state.hpp>
24 #include <boost/asio/detail/thread_context.hpp>
25 #include <boost/asio/detail/thread_info_base.hpp>
26 #include <boost/asio/detail/throw_error.hpp>
27 #include <boost/asio/detail/type_traits.hpp>
28 #include <boost/asio/error.hpp>
29 #include <boost/asio/post.hpp>
30 #include <boost/system/system_error.hpp>
31 #include <boost/asio/this_coro.hpp>
33 #include <boost/asio/detail/push_options.hpp>
39 struct awaitable_thread_has_context_switched {};
41 // An awaitable_thread represents a thread-of-execution that is composed of one
42 // or more "stack frames", with each frame represented by an awaitable_frame.
43 // All execution occurs in the context of the awaitable_thread's executor. An
44 // awaitable_thread continues to "pump" the stack frames by repeatedly resuming
45 // the top stack frame until the stack is empty, or until ownership of the
46 // stack is transferred to another awaitable_thread object.
48 // +------------------------------------+
51 // +--------------+---+ +-----------------+
53 // | awaitable_thread |<---------------------------+ awaitable_frame |
54 // | | attached_thread_ | |
55 // +--------------+---+ (Set only when +---+-------------+
56 // | frames are being |
57 // | actively pumped | caller_
58 // | by a thread, and |
60 // | the top frame.) +-----------------+
62 // | | awaitable_frame |
64 // | +---+-------------+
71 // | +-----------------+
72 // | bottom_of_stack_ | |
73 // +------------------------------->| awaitable_frame |
75 // +-----------------+
77 template <typename Executor>
78 class awaitable_frame_base
81 #if !defined(BOOST_ASIO_DISABLE_AWAITABLE_FRAME_RECYCLING)
82 void* operator new(std::size_t size)
84 return boost::asio::detail::thread_info_base::allocate(
85 boost::asio::detail::thread_info_base::awaitable_frame_tag(),
86 boost::asio::detail::thread_context::top_of_thread_call_stack(),
90 void operator delete(void* pointer, std::size_t size)
92 boost::asio::detail::thread_info_base::deallocate(
93 boost::asio::detail::thread_info_base::awaitable_frame_tag(),
94 boost::asio::detail::thread_context::top_of_thread_call_stack(),
97 #endif // !defined(BOOST_ASIO_DISABLE_AWAITABLE_FRAME_RECYCLING)
99 // The frame starts in a suspended state until the awaitable_thread object
101 auto initial_suspend() noexcept
103 return suspend_always();
106 // On final suspension the frame is popped from the top of the stack.
107 auto final_suspend() noexcept
111 awaitable_frame_base* this_;
113 bool await_ready() const noexcept
118 void await_suspend(coroutine_handle<void>) noexcept
120 this->this_->pop_frame();
123 void await_resume() const noexcept
131 void set_except(std::exception_ptr e) noexcept
133 pending_exception_ = e;
136 void set_error(const boost::system::error_code& ec)
138 this->set_except(std::make_exception_ptr(boost::system::system_error(ec)));
141 void unhandled_exception()
143 set_except(std::current_exception());
146 void rethrow_exception()
148 if (pending_exception_)
150 std::exception_ptr ex = std::exchange(pending_exception_, nullptr);
151 std::rethrow_exception(ex);
155 void clear_cancellation_slot()
157 this->attached_thread_->entry_point()->cancellation_state_.slot().clear();
160 template <typename T>
161 auto await_transform(awaitable<T, Executor> a) const
163 if (attached_thread_->entry_point()->throw_if_cancelled_)
164 if (!!attached_thread_->get_cancellation_state().cancelled())
165 do_throw_error(boost::asio::error::operation_aborted, "co_await");
169 // This await transformation obtains the associated executor of the thread of
171 auto await_transform(this_coro::executor_t) noexcept
175 awaitable_frame_base* this_;
177 bool await_ready() const noexcept
182 void await_suspend(coroutine_handle<void>) noexcept
186 auto await_resume() const noexcept
188 return this_->attached_thread_->get_executor();
195 // This await transformation obtains the associated cancellation state of the
196 // thread of execution.
197 auto await_transform(this_coro::cancellation_state_t) noexcept
201 awaitable_frame_base* this_;
203 bool await_ready() const noexcept
208 void await_suspend(coroutine_handle<void>) noexcept
212 auto await_resume() const noexcept
214 return this_->attached_thread_->get_cancellation_state();
221 // This await transformation resets the associated cancellation state.
222 auto await_transform(this_coro::reset_cancellation_state_0_t) noexcept
226 awaitable_frame_base* this_;
228 bool await_ready() const noexcept
233 void await_suspend(coroutine_handle<void>) noexcept
237 auto await_resume() const
239 return this_->attached_thread_->reset_cancellation_state();
246 // This await transformation resets the associated cancellation state.
247 template <typename Filter>
248 auto await_transform(
249 this_coro::reset_cancellation_state_1_t<Filter> reset) noexcept
253 awaitable_frame_base* this_;
256 bool await_ready() const noexcept
261 void await_suspend(coroutine_handle<void>) noexcept
267 return this_->attached_thread_->reset_cancellation_state(
268 BOOST_ASIO_MOVE_CAST(Filter)(filter_));
272 return result{this, BOOST_ASIO_MOVE_CAST(Filter)(reset.filter)};
275 // This await transformation resets the associated cancellation state.
276 template <typename InFilter, typename OutFilter>
277 auto await_transform(
278 this_coro::reset_cancellation_state_2_t<InFilter, OutFilter> reset)
283 awaitable_frame_base* this_;
285 OutFilter out_filter_;
287 bool await_ready() const noexcept
292 void await_suspend(coroutine_handle<void>) noexcept
298 return this_->attached_thread_->reset_cancellation_state(
299 BOOST_ASIO_MOVE_CAST(InFilter)(in_filter_),
300 BOOST_ASIO_MOVE_CAST(OutFilter)(out_filter_));
305 BOOST_ASIO_MOVE_CAST(InFilter)(reset.in_filter),
306 BOOST_ASIO_MOVE_CAST(OutFilter)(reset.out_filter)};
309 // This await transformation determines whether cancellation is propagated as
311 auto await_transform(this_coro::throw_if_cancelled_0_t)
316 awaitable_frame_base* this_;
318 bool await_ready() const noexcept
323 void await_suspend(coroutine_handle<void>) noexcept
329 return this_->attached_thread_->throw_if_cancelled();
336 // This await transformation sets whether cancellation is propagated as an
338 auto await_transform(this_coro::throw_if_cancelled_1_t throw_if_cancelled)
343 awaitable_frame_base* this_;
346 bool await_ready() const noexcept
351 void await_suspend(coroutine_handle<void>) noexcept
357 this_->attached_thread_->throw_if_cancelled(value_);
361 return result{this, throw_if_cancelled.value};
364 // This await transformation is used to run an async operation's initiation
365 // function object after the coroutine has been suspended. This ensures that
366 // immediate resumption of the coroutine in another thread does not cause a
368 template <typename Function>
369 auto await_transform(Function f,
372 typename result_of<Function(awaitable_frame_base*)>::type,
373 awaitable_thread<Executor>*
380 awaitable_frame_base* this_;
382 bool await_ready() const noexcept
387 void await_suspend(coroutine_handle<void>) noexcept
392 void await_resume() const noexcept
397 return result{std::move(f), this};
400 // Access the awaitable thread's has_context_switched_ flag.
401 auto await_transform(detail::awaitable_thread_has_context_switched) noexcept
405 awaitable_frame_base* this_;
407 bool await_ready() const noexcept
412 void await_suspend(coroutine_handle<void>) noexcept
416 bool& await_resume() const noexcept
418 return this_->attached_thread_->entry_point()->has_context_switched_;
425 void attach_thread(awaitable_thread<Executor>* handler) noexcept
427 attached_thread_ = handler;
430 awaitable_thread<Executor>* detach_thread() noexcept
432 attached_thread_->entry_point()->has_context_switched_ = true;
433 return std::exchange(attached_thread_, nullptr);
436 void push_frame(awaitable_frame_base<Executor>* caller) noexcept
439 attached_thread_ = caller_->attached_thread_;
440 attached_thread_->entry_point()->top_of_stack_ = this;
441 caller_->attached_thread_ = nullptr;
444 void pop_frame() noexcept
447 caller_->attached_thread_ = attached_thread_;
448 attached_thread_->entry_point()->top_of_stack_ = caller_;
449 attached_thread_ = nullptr;
464 coroutine_handle<void> coro_ = nullptr;
465 awaitable_thread<Executor>* attached_thread_ = nullptr;
466 awaitable_frame_base<Executor>* caller_ = nullptr;
467 std::exception_ptr pending_exception_ = nullptr;
470 template <typename T, typename Executor>
471 class awaitable_frame
472 : public awaitable_frame_base<Executor>
475 awaitable_frame() noexcept
479 awaitable_frame(awaitable_frame&& other) noexcept
480 : awaitable_frame_base<Executor>(std::move(other))
487 static_cast<T*>(static_cast<void*>(result_))->~T();
490 awaitable<T, Executor> get_return_object() noexcept
492 this->coro_ = coroutine_handle<awaitable_frame>::from_promise(*this);
493 return awaitable<T, Executor>(this);
496 template <typename U>
497 void return_value(U&& u)
499 new (&result_) T(std::forward<U>(u));
503 template <typename... Us>
504 void return_values(Us&&... us)
506 this->return_value(std::forward_as_tuple(std::forward<Us>(us)...));
511 this->caller_ = nullptr;
512 this->rethrow_exception();
513 return std::move(*static_cast<T*>(static_cast<void*>(result_)));
517 alignas(T) unsigned char result_[sizeof(T)];
518 bool has_result_ = false;
521 template <typename Executor>
522 class awaitable_frame<void, Executor>
523 : public awaitable_frame_base<Executor>
526 awaitable<void, Executor> get_return_object()
528 this->coro_ = coroutine_handle<awaitable_frame>::from_promise(*this);
529 return awaitable<void, Executor>(this);
538 this->caller_ = nullptr;
539 this->rethrow_exception();
543 struct awaitable_thread_entry_point {};
545 template <typename Executor>
546 class awaitable_frame<awaitable_thread_entry_point, Executor>
547 : public awaitable_frame_base<Executor>
552 has_executor_(false),
553 has_context_switched_(false),
554 throw_if_cancelled_(true)
561 u_.executor_.~Executor();
564 awaitable<awaitable_thread_entry_point, Executor> get_return_object()
566 this->coro_ = coroutine_handle<awaitable_frame>::from_promise(*this);
567 return awaitable<awaitable_thread_entry_point, Executor>(this);
576 this->caller_ = nullptr;
577 this->rethrow_exception();
581 template <typename> friend class awaitable_frame_base;
582 template <typename, typename> friend class awaitable_handler_base;
583 template <typename> friend class awaitable_thread;
593 awaitable_frame_base<Executor>* top_of_stack_;
594 boost::asio::cancellation_slot parent_cancellation_slot_;
595 boost::asio::cancellation_state cancellation_state_;
597 bool has_context_switched_;
598 bool throw_if_cancelled_;
601 template <typename Executor>
602 class awaitable_thread
605 typedef Executor executor_type;
606 typedef cancellation_slot cancellation_slot_type;
608 // Construct from the entry point of a new thread of execution.
609 awaitable_thread(awaitable<awaitable_thread_entry_point, Executor> p,
610 const Executor& ex, cancellation_slot parent_cancel_slot,
611 cancellation_state cancel_state)
612 : bottom_of_stack_(std::move(p))
614 bottom_of_stack_.frame_->top_of_stack_ = bottom_of_stack_.frame_;
615 new (&bottom_of_stack_.frame_->u_.executor_) Executor(ex);
616 bottom_of_stack_.frame_->has_executor_ = true;
617 bottom_of_stack_.frame_->parent_cancellation_slot_ = parent_cancel_slot;
618 bottom_of_stack_.frame_->cancellation_state_ = cancel_state;
621 // Transfer ownership from another awaitable_thread.
622 awaitable_thread(awaitable_thread&& other) noexcept
623 : bottom_of_stack_(std::move(other.bottom_of_stack_))
627 // Clean up with a last ditch effort to ensure the thread is unwound within
628 // the context of the executor.
631 if (bottom_of_stack_.valid())
633 // Coroutine "stack unwinding" must be performed through the executor.
634 auto* bottom_frame = bottom_of_stack_.frame_;
635 (post)(bottom_frame->u_.executor_,
636 [a = std::move(bottom_of_stack_)]() mutable
638 (void)awaitable<awaitable_thread_entry_point, Executor>(
644 awaitable_frame<awaitable_thread_entry_point, Executor>* entry_point()
646 return bottom_of_stack_.frame_;
649 executor_type get_executor() const noexcept
651 return bottom_of_stack_.frame_->u_.executor_;
654 cancellation_state get_cancellation_state() const noexcept
656 return bottom_of_stack_.frame_->cancellation_state_;
659 void reset_cancellation_state()
661 bottom_of_stack_.frame_->cancellation_state_ =
662 cancellation_state(bottom_of_stack_.frame_->parent_cancellation_slot_);
665 template <typename Filter>
666 void reset_cancellation_state(BOOST_ASIO_MOVE_ARG(Filter) filter)
668 bottom_of_stack_.frame_->cancellation_state_ =
669 cancellation_state(bottom_of_stack_.frame_->parent_cancellation_slot_,
670 BOOST_ASIO_MOVE_CAST(Filter)(filter));
673 template <typename InFilter, typename OutFilter>
674 void reset_cancellation_state(BOOST_ASIO_MOVE_ARG(InFilter) in_filter,
675 BOOST_ASIO_MOVE_ARG(OutFilter) out_filter)
677 bottom_of_stack_.frame_->cancellation_state_ =
678 cancellation_state(bottom_of_stack_.frame_->parent_cancellation_slot_,
679 BOOST_ASIO_MOVE_CAST(InFilter)(in_filter),
680 BOOST_ASIO_MOVE_CAST(OutFilter)(out_filter));
683 bool throw_if_cancelled() const
685 return bottom_of_stack_.frame_->throw_if_cancelled_;
688 void throw_if_cancelled(bool value)
690 bottom_of_stack_.frame_->throw_if_cancelled_ = value;
693 cancellation_slot_type get_cancellation_slot() const noexcept
695 return bottom_of_stack_.frame_->cancellation_state_.slot();
698 // Launch a new thread of execution.
701 bottom_of_stack_.frame_->top_of_stack_->attach_thread(this);
706 template <typename> friend class awaitable_frame_base;
708 // Repeatedly resume the top stack frame until the stack is empty or until it
709 // has been transferred to another resumable_thread object.
713 bottom_of_stack_.frame_->top_of_stack_->resume();
714 while (bottom_of_stack_.frame_ && bottom_of_stack_.frame_->top_of_stack_);
716 if (bottom_of_stack_.frame_)
718 awaitable<awaitable_thread_entry_point, Executor> a(
719 std::move(bottom_of_stack_));
720 a.frame_->rethrow_exception();
724 awaitable<awaitable_thread_entry_point, Executor> bottom_of_stack_;
727 } // namespace detail
731 #if !defined(GENERATING_DOCUMENTATION)
732 # if defined(BOOST_ASIO_HAS_STD_COROUTINE)
736 template <typename T, typename Executor, typename... Args>
737 struct coroutine_traits<boost::asio::awaitable<T, Executor>, Args...>
739 typedef boost::asio::detail::awaitable_frame<T, Executor> promise_type;
744 # else // defined(BOOST_ASIO_HAS_STD_COROUTINE)
746 namespace std { namespace experimental {
748 template <typename T, typename Executor, typename... Args>
749 struct coroutine_traits<boost::asio::awaitable<T, Executor>, Args...>
751 typedef boost::asio::detail::awaitable_frame<T, Executor> promise_type;
754 }} // namespace std::experimental
756 # endif // defined(BOOST_ASIO_HAS_STD_COROUTINE)
757 #endif // !defined(GENERATING_DOCUMENTATION)
759 #include <boost/asio/detail/pop_options.hpp>
761 #endif // BOOST_ASIO_IMPL_AWAITABLE_HPP