// // impl/spawn.hpp // ~~~~~~~~~~~~~~ // // Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com) // // 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 ASIO_IMPL_SPAWN_HPP #define ASIO_IMPL_SPAWN_HPP #if defined(_MSC_VER) && (_MSC_VER >= 1200) # pragma once #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) #include "asio/detail/config.hpp" #include #include "asio/associated_allocator.hpp" #include "asio/associated_cancellation_slot.hpp" #include "asio/associated_executor.hpp" #include "asio/async_result.hpp" #include "asio/bind_executor.hpp" #include "asio/detail/atomic_count.hpp" #include "asio/detail/bind_handler.hpp" #include "asio/detail/handler_cont_helpers.hpp" #include "asio/detail/memory.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/type_traits.hpp" #include "asio/detail/utility.hpp" #include "asio/disposition.hpp" #include "asio/error.hpp" #include "asio/system_error.hpp" #if defined(ASIO_HAS_BOOST_CONTEXT_FIBER) # include #endif // defined(ASIO_HAS_BOOST_CONTEXT_FIBER) #include "asio/detail/push_options.hpp" namespace asio { namespace detail { #if !defined(ASIO_NO_EXCEPTIONS) inline void spawned_thread_rethrow(void* ex) { if (*static_cast(ex)) rethrow_exception(*static_cast(ex)); } #endif // !defined(ASIO_NO_EXCEPTIONS) #if defined(ASIO_HAS_BOOST_CONTEXT_FIBER) // Spawned thread implementation using Boost.Context's fiber. class spawned_fiber_thread : public spawned_thread_base { public: typedef boost::context::fiber fiber_type; spawned_fiber_thread(fiber_type&& caller) : caller_(static_cast(caller)), on_suspend_fn_(0), on_suspend_arg_(0) { } template static spawned_thread_base* spawn(allocator_arg_t, StackAllocator&& stack_allocator, F&& f, cancellation_slot parent_cancel_slot = cancellation_slot(), cancellation_state cancel_state = cancellation_state()) { spawned_fiber_thread* spawned_thread = 0; fiber_type callee(allocator_arg_t(), static_cast(stack_allocator), entry_point>( static_cast(f), &spawned_thread)); callee = fiber_type(static_cast(callee)).resume(); spawned_thread->callee_ = static_cast(callee); spawned_thread->parent_cancellation_slot_ = parent_cancel_slot; spawned_thread->cancellation_state_ = cancel_state; return spawned_thread; } template static spawned_thread_base* spawn(F&& f, cancellation_slot parent_cancel_slot = cancellation_slot(), cancellation_state cancel_state = cancellation_state()) { return spawn(allocator_arg_t(), boost::context::fixedsize_stack(), static_cast(f), parent_cancel_slot, cancel_state); } void resume() { callee_ = fiber_type(static_cast(callee_)).resume(); if (on_suspend_fn_) { void (*fn)(void*) = on_suspend_fn_; void* arg = on_suspend_arg_; on_suspend_fn_ = 0; fn(arg); } } void suspend_with(void (*fn)(void*), void* arg) { if (throw_if_cancelled_) if (!!cancellation_state_.cancelled()) throw_error(asio::error::operation_aborted, "yield"); has_context_switched_ = true; on_suspend_fn_ = fn; on_suspend_arg_ = arg; caller_ = fiber_type(static_cast(caller_)).resume(); } void destroy() { fiber_type callee = static_cast(callee_); if (terminal_) fiber_type(static_cast(callee)).resume(); } private: template class entry_point { public: template entry_point(F&& f, spawned_fiber_thread** spawned_thread_out) : function_(static_cast(f)), spawned_thread_out_(spawned_thread_out) { } fiber_type operator()(fiber_type&& caller) { Function function(static_cast(function_)); spawned_fiber_thread spawned_thread( static_cast(caller)); *spawned_thread_out_ = &spawned_thread; spawned_thread_out_ = 0; spawned_thread.suspend(); #if !defined(ASIO_NO_EXCEPTIONS) try #endif // !defined(ASIO_NO_EXCEPTIONS) { function(&spawned_thread); spawned_thread.terminal_ = true; spawned_thread.suspend(); } #if !defined(ASIO_NO_EXCEPTIONS) catch (const boost::context::detail::forced_unwind&) { throw; } catch (...) { exception_ptr ex = current_exception(); spawned_thread.terminal_ = true; spawned_thread.suspend_with(spawned_thread_rethrow, &ex); } #endif // !defined(ASIO_NO_EXCEPTIONS) return static_cast(spawned_thread.caller_); } private: Function function_; spawned_fiber_thread** spawned_thread_out_; }; fiber_type caller_; fiber_type callee_; void (*on_suspend_fn_)(void*); void* on_suspend_arg_; }; #endif // defined(ASIO_HAS_BOOST_CONTEXT_FIBER) #if defined(ASIO_HAS_BOOST_CONTEXT_FIBER) typedef spawned_fiber_thread default_spawned_thread_type; #else # error No spawn() implementation available #endif // Helper class to perform the initial resume on the correct executor. class spawned_thread_resumer { public: explicit spawned_thread_resumer(spawned_thread_base* spawned_thread) : spawned_thread_(spawned_thread) { } spawned_thread_resumer(spawned_thread_resumer&& other) noexcept : spawned_thread_(other.spawned_thread_) { other.spawned_thread_ = 0; } ~spawned_thread_resumer() { if (spawned_thread_) spawned_thread_->destroy(); } void operator()() { spawned_thread_->attach(&spawned_thread_); spawned_thread_->resume(); } private: spawned_thread_base* spawned_thread_; }; // Helper class to ensure spawned threads are destroyed on the correct executor. class spawned_thread_destroyer { public: explicit spawned_thread_destroyer(spawned_thread_base* spawned_thread) : spawned_thread_(spawned_thread) { spawned_thread->detach(); } spawned_thread_destroyer(spawned_thread_destroyer&& other) noexcept : spawned_thread_(other.spawned_thread_) { other.spawned_thread_ = 0; } ~spawned_thread_destroyer() { if (spawned_thread_) spawned_thread_->destroy(); } void operator()() { if (spawned_thread_) { spawned_thread_->destroy(); spawned_thread_ = 0; } } private: spawned_thread_base* spawned_thread_; }; // Base class for all completion handlers associated with a spawned thread. template class spawn_handler_base { public: typedef Executor executor_type; typedef cancellation_slot cancellation_slot_type; spawn_handler_base(const basic_yield_context& yield) : yield_(yield), spawned_thread_(yield.spawned_thread_) { spawned_thread_->detach(); } spawn_handler_base(spawn_handler_base&& other) noexcept : yield_(other.yield_), spawned_thread_(other.spawned_thread_) { other.spawned_thread_ = 0; } ~spawn_handler_base() { if (spawned_thread_) (post)(yield_.executor_, spawned_thread_destroyer(spawned_thread_)); } executor_type get_executor() const noexcept { return yield_.executor_; } cancellation_slot_type get_cancellation_slot() const noexcept { return spawned_thread_->get_cancellation_slot(); } void resume() { spawned_thread_resumer resumer(spawned_thread_); spawned_thread_ = 0; resumer(); } protected: const basic_yield_context& yield_; spawned_thread_base* spawned_thread_; }; // Completion handlers for when basic_yield_context is used as a token. template class spawn_handler; template class spawn_handler : public spawn_handler_base { public: typedef void return_type; struct result_type {}; spawn_handler(const basic_yield_context& yield, result_type&) : spawn_handler_base(yield) { } void operator()() { this->resume(); } static return_type on_resume(result_type&) { } }; template class spawn_handler : public spawn_handler_base { public: typedef void return_type; typedef asio::error_code* result_type; spawn_handler(const basic_yield_context& yield, result_type& result) : spawn_handler_base(yield), result_(result) { } void operator()(asio::error_code ec) { if (this->yield_.ec_) { *this->yield_.ec_ = ec; result_ = 0; } else result_ = &ec; this->resume(); } static return_type on_resume(result_type& result) { if (result) throw_error(*result); } private: result_type& result_; }; template class spawn_handler::value> > : public spawn_handler_base { public: typedef void return_type; typedef Disposition* result_type; spawn_handler(const basic_yield_context& yield, result_type& result) : spawn_handler_base(yield), result_(result) { } void operator()(Disposition d) { result_ = detail::addressof(d); this->resume(); } static return_type on_resume(result_type& result) { if (*result != no_error) asio::throw_exception(static_cast(*result)); } private: result_type& result_; }; template class spawn_handler::value> > : public spawn_handler_base { public: typedef T return_type; typedef return_type* result_type; spawn_handler(const basic_yield_context& yield, result_type& result) : spawn_handler_base(yield), result_(result) { } void operator()(T value) { result_ = detail::addressof(value); this->resume(); } static return_type on_resume(result_type& result) { return static_cast(*result); } private: result_type& result_; }; template class spawn_handler : public spawn_handler_base { public: typedef T return_type; struct result_type { asio::error_code* ec_; return_type* value_; }; spawn_handler(const basic_yield_context& yield, result_type& result) : spawn_handler_base(yield), result_(result) { } void operator()(asio::error_code ec, T value) { if (this->yield_.ec_) { *this->yield_.ec_ = ec; result_.ec_ = 0; } else result_.ec_ = &ec; result_.value_ = detail::addressof(value); this->resume(); } static return_type on_resume(result_type& result) { if (result.ec_) throw_error(*result.ec_); return static_cast(*result.value_); } private: result_type& result_; }; template class spawn_handler::value> > : public spawn_handler_base { public: typedef T return_type; struct result_type { Disposition* disposition_; return_type* value_; }; spawn_handler(const basic_yield_context& yield, result_type& result) : spawn_handler_base(yield), result_(result) { } void operator()(Disposition d, T value) { result_.disposition_ = detail::addressof(d); result_.value_ = detail::addressof(value); this->resume(); } static return_type on_resume(result_type& result) { if (*result.disposition_ != no_error) { asio::throw_exception( static_cast(*result.disposition_)); } return static_cast(*result.value_); } private: result_type& result_; }; template class spawn_handler::value> > : public spawn_handler_base { public: typedef std::tuple return_type; typedef return_type* result_type; spawn_handler(const basic_yield_context& yield, result_type& result) : spawn_handler_base(yield), result_(result) { } template void operator()(Args&&... args) { return_type value(static_cast(args)...); result_ = detail::addressof(value); this->resume(); } static return_type on_resume(result_type& result) { return static_cast(*result); } private: result_type& result_; }; template class spawn_handler : public spawn_handler_base { public: typedef std::tuple return_type; struct result_type { asio::error_code* ec_; return_type* value_; }; spawn_handler(const basic_yield_context& yield, result_type& result) : spawn_handler_base(yield), result_(result) { } template void operator()(asio::error_code ec, Args&&... args) { return_type value(static_cast(args)...); if (this->yield_.ec_) { *this->yield_.ec_ = ec; result_.ec_ = 0; } else result_.ec_ = &ec; result_.value_ = detail::addressof(value); this->resume(); } static return_type on_resume(result_type& result) { if (result.ec_) throw_error(*result.ec_); return static_cast(*result.value_); } private: result_type& result_; }; template class spawn_handler::value> > : public spawn_handler_base { public: typedef std::tuple return_type; struct result_type { Disposition* disposition_; return_type* value_; }; spawn_handler(const basic_yield_context& yield, result_type& result) : spawn_handler_base(yield), result_(result) { } template void operator()(Disposition d, Args&&... args) { return_type value(static_cast(args)...); result_.disposition_ = detail::addressof(d); result_.value_ = detail::addressof(value); this->resume(); } static return_type on_resume(result_type& result) { if (*result.disposition_ != no_error) { asio::throw_exception( static_cast(*result.disposition_)); } return static_cast(*result.value_); } private: result_type& result_; }; template inline bool asio_handler_is_continuation(spawn_handler*) { return true; } } // namespace detail #if !defined(GENERATING_DOCUMENTATION) template class async_result, Signature> { public: typedef typename detail::spawn_handler handler_type; typedef typename handler_type::return_type return_type; #if defined(ASIO_HAS_VARIADIC_LAMBDA_CAPTURES) template static return_type initiate(Initiation&& init, const basic_yield_context& yield, InitArgs&&... init_args) { typename handler_type::result_type result = typename handler_type::result_type(); yield.spawned_thread_->suspend_with( [&]() { static_cast(init)( handler_type(yield, result), static_cast(init_args)...); }); return handler_type::on_resume(result); } #else // defined(ASIO_HAS_VARIADIC_LAMBDA_CAPTURES) template struct suspend_with_helper { typename handler_type::result_type& result_; Initiation&& init_; const basic_yield_context& yield_; std::tuple init_args_; template void do_invoke(detail::index_sequence) { static_cast(init_)( handler_type(yield_, result_), static_cast(std::get(init_args_))...); } void operator()() { this->do_invoke(detail::make_index_sequence()); } }; template static return_type initiate(Initiation&& init, const basic_yield_context& yield, InitArgs&&... init_args) { typename handler_type::result_type result = typename handler_type::result_type(); yield.spawned_thread_->suspend_with( suspend_with_helper{ result, static_cast(init), yield, std::tuple( static_cast(init_args)...)}); return handler_type::on_resume(result); } #endif // defined(ASIO_HAS_VARIADIC_LAMBDA_CAPTURES) }; #endif // !defined(GENERATING_DOCUMENTATION) namespace detail { template class spawn_entry_point { public: template spawn_entry_point(const Executor& ex, F&& f, H&& h) : executor_(ex), function_(static_cast(f)), handler_(static_cast(h)), work_(handler_, executor_) { } void operator()(spawned_thread_base* spawned_thread) { const basic_yield_context yield(spawned_thread, executor_); this->call(yield, void_type)>>()); } private: void call(const basic_yield_context& yield, void_type) { #if !defined(ASIO_NO_EXCEPTIONS) try #endif // !defined(ASIO_NO_EXCEPTIONS) { function_(yield); if (!yield.spawned_thread_->has_context_switched()) (post)(yield); detail::binder1 handler(handler_, exception_ptr()); work_.complete(handler, handler.handler_); } #if !defined(ASIO_NO_EXCEPTIONS) # if defined(ASIO_HAS_BOOST_CONTEXT_FIBER) catch (const boost::context::detail::forced_unwind&) { throw; } # endif // defined(ASIO_HAS_BOOST_CONTEXT_FIBER) catch (...) { exception_ptr ex = current_exception(); if (!yield.spawned_thread_->has_context_switched()) (post)(yield); detail::binder1 handler(handler_, ex); work_.complete(handler, handler.handler_); } #endif // !defined(ASIO_NO_EXCEPTIONS) } template void call(const basic_yield_context& yield, void_type) { #if !defined(ASIO_NO_EXCEPTIONS) try #endif // !defined(ASIO_NO_EXCEPTIONS) { T result(function_(yield)); if (!yield.spawned_thread_->has_context_switched()) (post)(yield); detail::move_binder2 handler(0, static_cast(handler_), exception_ptr(), static_cast(result)); work_.complete(handler, handler.handler_); } #if !defined(ASIO_NO_EXCEPTIONS) # if defined(ASIO_HAS_BOOST_CONTEXT_FIBER) catch (const boost::context::detail::forced_unwind&) { throw; } # endif // defined(ASIO_HAS_BOOST_CONTEXT_FIBER) catch (...) { exception_ptr ex = current_exception(); if (!yield.spawned_thread_->has_context_switched()) (post)(yield); detail::move_binder2 handler(0, static_cast(handler_), ex, T()); work_.complete(handler, handler.handler_); } #endif // !defined(ASIO_NO_EXCEPTIONS) } Executor executor_; Function function_; Handler handler_; handler_work work_; }; struct spawn_cancellation_signal_emitter { cancellation_signal* signal_; cancellation_type_t type_; void operator()() { signal_->emit(type_); } }; template class spawn_cancellation_handler { public: spawn_cancellation_handler(const Handler&, const Executor& ex) : ex_(ex) { } cancellation_slot slot() { return signal_.slot(); } void operator()(cancellation_type_t type) { spawn_cancellation_signal_emitter emitter = { &signal_, type }; (dispatch)(ex_, emitter); } private: cancellation_signal signal_; Executor ex_; }; template class spawn_cancellation_handler::asio_associated_executor_is_unspecialised, void >::value >> { public: spawn_cancellation_handler(const Handler&, const Executor&) { } cancellation_slot slot() { return signal_.slot(); } void operator()(cancellation_type_t type) { signal_.emit(type); } private: cancellation_signal signal_; }; template class initiate_spawn { public: typedef Executor executor_type; explicit initiate_spawn(const executor_type& ex) : executor_(ex) { } executor_type get_executor() const noexcept { return executor_; } template void operator()(Handler&& handler, F&& f) const { typedef decay_t handler_type; typedef decay_t function_type; typedef spawn_cancellation_handler< handler_type, Executor> cancel_handler_type; associated_cancellation_slot_t slot = asio::get_associated_cancellation_slot(handler); cancel_handler_type* cancel_handler = slot.is_connected() ? &slot.template emplace(handler, executor_) : 0; cancellation_slot proxy_slot( cancel_handler ? cancel_handler->slot() : cancellation_slot()); cancellation_state cancel_state(proxy_slot); (dispatch)(executor_, spawned_thread_resumer( default_spawned_thread_type::spawn( spawn_entry_point( executor_, static_cast(f), static_cast(handler)), proxy_slot, cancel_state))); } #if defined(ASIO_HAS_BOOST_CONTEXT_FIBER) template void operator()(Handler&& handler, allocator_arg_t, StackAllocator&& stack_allocator, F&& f) const { typedef decay_t handler_type; typedef decay_t function_type; typedef spawn_cancellation_handler< handler_type, Executor> cancel_handler_type; associated_cancellation_slot_t slot = asio::get_associated_cancellation_slot(handler); cancel_handler_type* cancel_handler = slot.is_connected() ? &slot.template emplace(handler, executor_) : 0; cancellation_slot proxy_slot( cancel_handler ? cancel_handler->slot() : cancellation_slot()); cancellation_state cancel_state(proxy_slot); (dispatch)(executor_, spawned_thread_resumer( spawned_fiber_thread::spawn(allocator_arg_t(), static_cast(stack_allocator), spawn_entry_point( executor_, static_cast(f), static_cast(handler)), proxy_slot, cancel_state))); } #endif // defined(ASIO_HAS_BOOST_CONTEXT_FIBER) private: executor_type executor_; }; } // namespace detail template )>>::type) CompletionToken> inline auto spawn(const Executor& ex, F&& function, CompletionToken&& token, constraint_t< is_executor::value || execution::is_executor::value >) -> decltype( async_initiate)>>::type>( declval>(), token, static_cast(function))) { return async_initiate)>>::type>( detail::initiate_spawn(ex), token, static_cast(function)); } template )>>::type) CompletionToken> inline auto spawn(ExecutionContext& ctx, F&& function, CompletionToken&& token, constraint_t< is_convertible::value >) -> decltype( async_initiate)>>::type>( declval>(), token, static_cast(function))) { return (spawn)(ctx.get_executor(), static_cast(function), static_cast(token)); } template )>>::type) CompletionToken> inline auto spawn(const basic_yield_context& ctx, F&& function, CompletionToken&& token, constraint_t< is_executor::value || execution::is_executor::value >) -> decltype( async_initiate)>>::type>( declval>(), token, static_cast(function))) { return (spawn)(ctx.get_executor(), static_cast(function), static_cast(token)); } #if defined(ASIO_HAS_BOOST_CONTEXT_FIBER) template )>>::type) CompletionToken> inline auto spawn(const Executor& ex, allocator_arg_t, StackAllocator&& stack_allocator, F&& function, CompletionToken&& token, constraint_t< is_executor::value || execution::is_executor::value >) -> decltype( async_initiate)>>::type>( declval>(), token, allocator_arg_t(), static_cast(stack_allocator), static_cast(function))) { return async_initiate)>>::type>( detail::initiate_spawn(ex), token, allocator_arg_t(), static_cast(stack_allocator), static_cast(function)); } template )>>::type) CompletionToken> inline auto spawn(ExecutionContext& ctx, allocator_arg_t, StackAllocator&& stack_allocator, F&& function, CompletionToken&& token, constraint_t< is_convertible::value >) -> decltype( async_initiate)>>::type>( declval>(), token, allocator_arg_t(), static_cast(stack_allocator), static_cast(function))) { return (spawn)(ctx.get_executor(), allocator_arg_t(), static_cast(stack_allocator), static_cast(function), static_cast(token)); } template )>>::type) CompletionToken> inline auto spawn(const basic_yield_context& ctx, allocator_arg_t, StackAllocator&& stack_allocator, F&& function, CompletionToken&& token, constraint_t< is_executor::value || execution::is_executor::value >) -> decltype( async_initiate)>>::type>( declval>(), token, allocator_arg_t(), static_cast(stack_allocator), static_cast(function))) { return (spawn)(ctx.get_executor(), allocator_arg_t(), static_cast(stack_allocator), static_cast(function), static_cast(token)); } #endif // defined(ASIO_HAS_BOOST_CONTEXT_FIBER) } // namespace asio #include "asio/detail/pop_options.hpp" #endif // ASIO_IMPL_SPAWN_HPP