//
// impl/spawn.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2023 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 <tuple>
#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/system_error.hpp"

#if defined(ASIO_HAS_BOOST_CONTEXT_FIBER)
# include <boost/context/fiber.hpp>
#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<exception_ptr*>(ex))
    rethrow_exception(*static_cast<exception_ptr*>(ex));
}
#endif // !defined(ASIO_NO_EXCEPTIONS)

#if defined(ASIO_HAS_BOOST_COROUTINE)

// Spawned thread implementation using Boost.Coroutine.
class spawned_coroutine_thread : public spawned_thread_base
{
public:
#if defined(BOOST_COROUTINES_UNIDIRECT) || defined(BOOST_COROUTINES_V2)
  typedef boost::coroutines::pull_coroutine<void> callee_type;
  typedef boost::coroutines::push_coroutine<void> caller_type;
#else
  typedef boost::coroutines::coroutine<void()> callee_type;
  typedef boost::coroutines::coroutine<void()> caller_type;
#endif

  spawned_coroutine_thread(caller_type& caller)
    : caller_(caller),
      on_suspend_fn_(0),
      on_suspend_arg_(0)
  {
  }

  template <typename F>
  static spawned_thread_base* spawn(F&& f,
      const boost::coroutines::attributes& attributes,
      cancellation_slot parent_cancel_slot = cancellation_slot(),
      cancellation_state cancel_state = cancellation_state())
  {
    spawned_coroutine_thread* spawned_thread = 0;
    callee_type callee(entry_point<decay_t<F>>(
          static_cast<F&&>(f), &spawned_thread), attributes);
    spawned_thread->callee_.swap(callee);
    spawned_thread->parent_cancellation_slot_ = parent_cancel_slot;
    spawned_thread->cancellation_state_ = cancel_state;
    return spawned_thread;
  }

  template <typename F>
  static spawned_thread_base* spawn(F&& f,
      cancellation_slot parent_cancel_slot = cancellation_slot(),
      cancellation_state cancel_state = cancellation_state())
  {
    return spawn(static_cast<F&&>(f), boost::coroutines::attributes(),
        parent_cancel_slot, cancel_state);
  }

  void resume()
  {
    callee_();
    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_();
  }

  void destroy()
  {
    callee_type callee;
    callee.swap(callee_);
    if (terminal_)
      callee();
  }

private:
  template <typename Function>
  class entry_point
  {
  public:
    template <typename F>
    entry_point(F&& f,
        spawned_coroutine_thread** spawned_thread_out)
      : function_(static_cast<F&&>(f)),
        spawned_thread_out_(spawned_thread_out)
    {
    }

    void operator()(caller_type& caller)
    {
      Function function(static_cast<Function&&>(function_));
      spawned_coroutine_thread spawned_thread(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::coroutines::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)
    }

  private:
    Function function_;
    spawned_coroutine_thread** spawned_thread_out_;
  };

  caller_type& caller_;
  callee_type callee_;
  void (*on_suspend_fn_)(void*);
  void* on_suspend_arg_;
};

#endif // defined(ASIO_HAS_BOOST_COROUTINE)

#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<fiber_type&&>(caller)),
      on_suspend_fn_(0),
      on_suspend_arg_(0)
  {
  }

  template <typename StackAllocator, typename F>
  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<StackAllocator&&>(stack_allocator),
        entry_point<decay_t<F>>(
          static_cast<F&&>(f), &spawned_thread));
    callee = fiber_type(static_cast<fiber_type&&>(callee)).resume();
    spawned_thread->callee_ = static_cast<fiber_type&&>(callee);
    spawned_thread->parent_cancellation_slot_ = parent_cancel_slot;
    spawned_thread->cancellation_state_ = cancel_state;
    return spawned_thread;
  }

  template <typename F>
  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&&>(f), parent_cancel_slot, cancel_state);
  }

  void resume()
  {
    callee_ = fiber_type(static_cast<fiber_type&&>(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<fiber_type&&>(caller_)).resume();
  }

  void destroy()
  {
    fiber_type callee = static_cast<fiber_type&&>(callee_);
    if (terminal_)
      fiber_type(static_cast<fiber_type&&>(callee)).resume();
  }

private:
  template <typename Function>
  class entry_point
  {
  public:
    template <typename F>
    entry_point(F&& f,
        spawned_fiber_thread** spawned_thread_out)
      : function_(static_cast<F&&>(f)),
        spawned_thread_out_(spawned_thread_out)
    {
    }

    fiber_type operator()(fiber_type&& caller)
    {
      Function function(static_cast<Function&&>(function_));
      spawned_fiber_thread spawned_thread(
          static_cast<fiber_type&&>(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<fiber_type&&>(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;
#elif defined(ASIO_HAS_BOOST_COROUTINE)
typedef spawned_coroutine_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 <typename Executor>
class spawn_handler_base
{
public:
  typedef Executor executor_type;
  typedef cancellation_slot cancellation_slot_type;

  spawn_handler_base(const basic_yield_context<Executor>& 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<Executor>& yield_;
  spawned_thread_base* spawned_thread_;
};

// Completion handlers for when basic_yield_context is used as a token.
template <typename Executor, typename Signature>
class spawn_handler;

template <typename Executor, typename R>
class spawn_handler<Executor, R()>
  : public spawn_handler_base<Executor>
{
public:
  typedef void return_type;

  struct result_type {};

  spawn_handler(const basic_yield_context<Executor>& yield, result_type&)
    : spawn_handler_base<Executor>(yield)
  {
  }

  void operator()()
  {
    this->resume();
  }

  static return_type on_resume(result_type&)
  {
  }
};

template <typename Executor, typename R>
class spawn_handler<Executor, R(asio::error_code)>
  : public spawn_handler_base<Executor>
{
public:
  typedef void return_type;
  typedef asio::error_code* result_type;

  spawn_handler(const basic_yield_context<Executor>& yield, result_type& result)
    : spawn_handler_base<Executor>(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 <typename Executor, typename R>
class spawn_handler<Executor, R(exception_ptr)>
  : public spawn_handler_base<Executor>
{
public:
  typedef void return_type;
  typedef exception_ptr* result_type;

  spawn_handler(const basic_yield_context<Executor>& yield, result_type& result)
    : spawn_handler_base<Executor>(yield),
      result_(result)
  {
  }

  void operator()(exception_ptr ex)
  {
    result_ = &ex;
    this->resume();
  }

  static return_type on_resume(result_type& result)
  {
    if (*result)
      rethrow_exception(*result);
  }

private:
  result_type& result_;
};

template <typename Executor, typename R, typename T>
class spawn_handler<Executor, R(T)>
  : public spawn_handler_base<Executor>
{
public:
  typedef T return_type;
  typedef return_type* result_type;

  spawn_handler(const basic_yield_context<Executor>& yield, result_type& result)
    : spawn_handler_base<Executor>(yield),
      result_(result)
  {
  }

  void operator()(T value)
  {
    result_ = &value;
    this->resume();
  }

  static return_type on_resume(result_type& result)
  {
    return static_cast<return_type&&>(*result);
  }

private:
  result_type& result_;
};

template <typename Executor, typename R, typename T>
class spawn_handler<Executor, R(asio::error_code, T)>
  : public spawn_handler_base<Executor>
{
public:
  typedef T return_type;

  struct result_type
  {
    asio::error_code* ec_;
    return_type* value_;
  };

  spawn_handler(const basic_yield_context<Executor>& yield, result_type& result)
    : spawn_handler_base<Executor>(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_ = &value;
    this->resume();
  }

  static return_type on_resume(result_type& result)
  {
    if (result.ec_)
      throw_error(*result.ec_);
    return static_cast<return_type&&>(*result.value_);
  }

private:
  result_type& result_;
};

template <typename Executor, typename R, typename T>
class spawn_handler<Executor, R(exception_ptr, T)>
  : public spawn_handler_base<Executor>
{
public:
  typedef T return_type;

  struct result_type
  {
    exception_ptr* ex_;
    return_type* value_;
  };

  spawn_handler(const basic_yield_context<Executor>& yield, result_type& result)
    : spawn_handler_base<Executor>(yield),
      result_(result)
  {
  }

  void operator()(exception_ptr ex, T value)
  {
    result_.ex_ = &ex;
    result_.value_ = &value;
    this->resume();
  }

  static return_type on_resume(result_type& result)
  {
    if (*result.ex_)
      rethrow_exception(*result.ex_);
    return static_cast<return_type&&>(*result.value_);
  }

private:
  result_type& result_;
};

template <typename Executor, typename R, typename... Ts>
class spawn_handler<Executor, R(Ts...)>
  : public spawn_handler_base<Executor>
{
public:
  typedef std::tuple<Ts...> return_type;

  typedef return_type* result_type;

  spawn_handler(const basic_yield_context<Executor>& yield, result_type& result)
    : spawn_handler_base<Executor>(yield),
      result_(result)
  {
  }

  template <typename... Args>
  void operator()(Args&&... args)
  {
    return_type value(static_cast<Args&&>(args)...);
    result_ = &value;
    this->resume();
  }

  static return_type on_resume(result_type& result)
  {
    return static_cast<return_type&&>(*result);
  }

private:
  result_type& result_;
};

template <typename Executor, typename R, typename... Ts>
class spawn_handler<Executor, R(asio::error_code, Ts...)>
  : public spawn_handler_base<Executor>
{
public:
  typedef std::tuple<Ts...> return_type;

  struct result_type
  {
    asio::error_code* ec_;
    return_type* value_;
  };

  spawn_handler(const basic_yield_context<Executor>& yield, result_type& result)
    : spawn_handler_base<Executor>(yield),
      result_(result)
  {
  }

  template <typename... Args>
  void operator()(asio::error_code ec,
      Args&&... args)
  {
    return_type value(static_cast<Args&&>(args)...);
    if (this->yield_.ec_)
    {
      *this->yield_.ec_ = ec;
      result_.ec_ = 0;
    }
    else
      result_.ec_ = &ec;
    result_.value_ = &value;
    this->resume();
  }

  static return_type on_resume(result_type& result)
  {
    if (result.ec_)
      throw_error(*result.ec_);
    return static_cast<return_type&&>(*result.value_);
  }

private:
  result_type& result_;
};

template <typename Executor, typename R, typename... Ts>
class spawn_handler<Executor, R(exception_ptr, Ts...)>
  : public spawn_handler_base<Executor>
{
public:
  typedef std::tuple<Ts...> return_type;

  struct result_type
  {
    exception_ptr* ex_;
    return_type* value_;
  };

  spawn_handler(const basic_yield_context<Executor>& yield, result_type& result)
    : spawn_handler_base<Executor>(yield),
      result_(result)
  {
  }

  template <typename... Args>
  void operator()(exception_ptr ex, Args&&... args)
  {
    return_type value(static_cast<Args&&>(args)...);
    result_.ex_ = &ex;
    result_.value_ = &value;
    this->resume();
  }

  static return_type on_resume(result_type& result)
  {
    if (*result.ex_)
      rethrow_exception(*result.ex_);
    return static_cast<return_type&&>(*result.value_);
  }

private:
  result_type& result_;
};

template <typename Executor, typename Signature>
inline bool asio_handler_is_continuation(spawn_handler<Executor, Signature>*)
{
  return true;
}

} // namespace detail

template <typename Executor, typename Signature>
class async_result<basic_yield_context<Executor>, Signature>
{
public:
  typedef typename detail::spawn_handler<Executor, Signature> handler_type;
  typedef typename handler_type::return_type return_type;

#if defined(ASIO_HAS_VARIADIC_LAMBDA_CAPTURES)

  template <typename Initiation, typename... InitArgs>
  static return_type initiate(Initiation&& init,
      const basic_yield_context<Executor>& yield,
      InitArgs&&... init_args)
  {
    typename handler_type::result_type result
      = typename handler_type::result_type();

    yield.spawned_thread_->suspend_with(
        [&]()
        {
          static_cast<Initiation&&>(init)(
              handler_type(yield, result),
              static_cast<InitArgs&&>(init_args)...);
        });

    return handler_type::on_resume(result);
  }

#else // defined(ASIO_HAS_VARIADIC_LAMBDA_CAPTURES)

  template <typename Initiation, typename... InitArgs>
  struct suspend_with_helper
  {
    typename handler_type::result_type& result_;
    Initiation&& init_;
    const basic_yield_context<Executor>& yield_;
    std::tuple<InitArgs&&...> init_args_;

    template <std::size_t... I>
    void do_invoke(detail::index_sequence<I...>)
    {
      static_cast<Initiation&&>(init_)(
          handler_type(yield_, result_),
          static_cast<InitArgs&&>(std::get<I>(init_args_))...);
    }

    void operator()()
    {
      this->do_invoke(detail::make_index_sequence<sizeof...(InitArgs)>());
    }
  };

  template <typename Initiation, typename... InitArgs>
  static return_type initiate(Initiation&& init,
      const basic_yield_context<Executor>& yield,
      InitArgs&&... init_args)
  {
    typename handler_type::result_type result
      = typename handler_type::result_type();

    yield.spawned_thread_->suspend_with(
      suspend_with_helper<Initiation, InitArgs...>{
          result, static_cast<Initiation&&>(init), yield,
          std::tuple<InitArgs&&...>(
            static_cast<InitArgs&&>(init_args)...)});

    return handler_type::on_resume(result);
  }

#endif // defined(ASIO_HAS_VARIADIC_LAMBDA_CAPTURES)
};

namespace detail {

template <typename Executor, typename Function, typename Handler>
class spawn_entry_point
{
public:
  template <typename F, typename H>
  spawn_entry_point(const Executor& ex,
      F&& f, H&& h)
    : executor_(ex),
      function_(static_cast<F&&>(f)),
      handler_(static_cast<H&&>(h)),
      work_(handler_, executor_)
  {
  }

  void operator()(spawned_thread_base* spawned_thread)
  {
    const basic_yield_context<Executor> yield(spawned_thread, executor_);
    this->call(yield,
        void_type<result_of_t<Function(basic_yield_context<Executor>)>>());
  }

private:
  void call(const basic_yield_context<Executor>& yield, void_type<void>)
  {
#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, exception_ptr>
        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)
# if defined(ASIO_HAS_BOOST_COROUTINE)
    catch (const boost::coroutines::detail::forced_unwind&)
    {
      throw;
    }
# endif // defined(ASIO_HAS_BOOST_COROUTINE)
    catch (...)
    {
      exception_ptr ex = current_exception();
      if (!yield.spawned_thread_->has_context_switched())
        (post)(yield);
      detail::binder1<Handler, exception_ptr> handler(handler_, ex);
      work_.complete(handler, handler.handler_);
    }
#endif // !defined(ASIO_NO_EXCEPTIONS)
  }

  template <typename T>
  void call(const basic_yield_context<Executor>& yield, void_type<T>)
  {
#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::binder2<Handler, exception_ptr, T>
        handler(handler_, exception_ptr(), static_cast<T&&>(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)
# if defined(ASIO_HAS_BOOST_COROUTINE)
    catch (const boost::coroutines::detail::forced_unwind&)
    {
      throw;
    }
# endif // defined(ASIO_HAS_BOOST_COROUTINE)
    catch (...)
    {
      exception_ptr ex = current_exception();
      if (!yield.spawned_thread_->has_context_switched())
        (post)(yield);
      detail::binder2<Handler, exception_ptr, T> handler(handler_, ex, T());
      work_.complete(handler, handler.handler_);
    }
#endif // !defined(ASIO_NO_EXCEPTIONS)
  }

  Executor executor_;
  Function function_;
  Handler handler_;
  handler_work<Handler, Executor> work_;
};

struct spawn_cancellation_signal_emitter
{
  cancellation_signal* signal_;
  cancellation_type_t type_;

  void operator()()
  {
    signal_->emit(type_);
  }
};

template <typename Handler, typename Executor, typename = void>
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 <typename Handler, typename Executor>
class spawn_cancellation_handler<Handler, Executor,
    enable_if_t<
      is_same<
        typename associated_executor<Handler,
          Executor>::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 <typename Executor>
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 <typename Handler, typename F>
  void operator()(Handler&& handler,
      F&& f) const
  {
    typedef decay_t<Handler> handler_type;
    typedef decay_t<F> function_type;
    typedef spawn_cancellation_handler<
      handler_type, Executor> cancel_handler_type;

    associated_cancellation_slot_t<handler_type> slot
      = asio::get_associated_cancellation_slot(handler);

    cancel_handler_type* cancel_handler = slot.is_connected()
      ? &slot.template emplace<cancel_handler_type>(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, function_type, handler_type>(
              executor_, static_cast<F&&>(f),
              static_cast<Handler&&>(handler)),
            proxy_slot, cancel_state)));
  }

#if defined(ASIO_HAS_BOOST_CONTEXT_FIBER)

  template <typename Handler, typename StackAllocator, typename F>
  void operator()(Handler&& handler, allocator_arg_t,
      StackAllocator&& stack_allocator,
      F&& f) const
  {
    typedef decay_t<Handler> handler_type;
    typedef decay_t<F> function_type;
    typedef spawn_cancellation_handler<
      handler_type, Executor> cancel_handler_type;

    associated_cancellation_slot_t<handler_type> slot
      = asio::get_associated_cancellation_slot(handler);

    cancel_handler_type* cancel_handler = slot.is_connected()
      ? &slot.template emplace<cancel_handler_type>(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<StackAllocator&&>(stack_allocator),
            spawn_entry_point<Executor, function_type, handler_type>(
              executor_, static_cast<F&&>(f),
              static_cast<Handler&&>(handler)),
            proxy_slot, cancel_state)));
  }

#endif // defined(ASIO_HAS_BOOST_CONTEXT_FIBER)

private:
  executor_type executor_;
};

} // namespace detail

template <typename Executor, typename F,
    ASIO_COMPLETION_TOKEN_FOR(typename detail::spawn_signature<
      result_of_t<F(basic_yield_context<Executor>)>>::type) CompletionToken>
inline auto spawn(const Executor& ex, F&& function, CompletionToken&& token,
#if defined(ASIO_HAS_BOOST_COROUTINE)
    constraint_t<
      !is_same<
        decay_t<CompletionToken>,
        boost::coroutines::attributes
      >::value
    >,
#endif // defined(ASIO_HAS_BOOST_COROUTINE)
    constraint_t<
      is_executor<Executor>::value || execution::is_executor<Executor>::value
    >)
  -> decltype(
    async_initiate<CompletionToken,
      typename detail::spawn_signature<
        result_of_t<F(basic_yield_context<Executor>)>>::type>(
          declval<detail::initiate_spawn<Executor>>(),
          token, static_cast<F&&>(function)))
{
  return async_initiate<CompletionToken,
    typename detail::spawn_signature<
      result_of_t<F(basic_yield_context<Executor>)>>::type>(
        detail::initiate_spawn<Executor>(ex),
        token, static_cast<F&&>(function));
}

template <typename ExecutionContext, typename F,
    ASIO_COMPLETION_TOKEN_FOR(typename detail::spawn_signature<
      result_of_t<F(basic_yield_context<
        typename ExecutionContext::executor_type>)>>::type) CompletionToken>
inline auto spawn(ExecutionContext& ctx, F&& function, CompletionToken&& token,
#if defined(ASIO_HAS_BOOST_COROUTINE)
    constraint_t<
      !is_same<
        decay_t<CompletionToken>,
        boost::coroutines::attributes
      >::value
    >,
#endif // defined(ASIO_HAS_BOOST_COROUTINE)
    constraint_t<
      is_convertible<ExecutionContext&, execution_context&>::value
    >)
  -> decltype(
    async_initiate<CompletionToken,
      typename detail::spawn_signature<
        result_of_t<F(basic_yield_context<
          typename ExecutionContext::executor_type>)>>::type>(
            declval<detail::initiate_spawn<
              typename ExecutionContext::executor_type>>(),
            token, static_cast<F&&>(function)))
{
  return (spawn)(ctx.get_executor(), static_cast<F&&>(function),
      static_cast<CompletionToken&&>(token));
}

template <typename Executor, typename F,
    ASIO_COMPLETION_TOKEN_FOR(typename detail::spawn_signature<
      result_of_t<F(basic_yield_context<Executor>)>>::type)
        CompletionToken>
inline auto spawn(const basic_yield_context<Executor>& ctx,
    F&& function, CompletionToken&& token,
#if defined(ASIO_HAS_BOOST_COROUTINE)
    constraint_t<
      !is_same<
        decay_t<CompletionToken>,
        boost::coroutines::attributes
      >::value
    >,
#endif // defined(ASIO_HAS_BOOST_COROUTINE)
    constraint_t<
      is_executor<Executor>::value || execution::is_executor<Executor>::value
    >)
  -> decltype(
    async_initiate<CompletionToken,
      typename detail::spawn_signature<
        result_of_t<F(basic_yield_context<Executor>)>>::type>(
          declval<detail::initiate_spawn<Executor>>(),
          token, static_cast<F&&>(function)))
{
  return (spawn)(ctx.get_executor(), static_cast<F&&>(function),
      static_cast<CompletionToken&&>(token));
}

#if defined(ASIO_HAS_BOOST_CONTEXT_FIBER)

template <typename Executor, typename StackAllocator, typename F,
    ASIO_COMPLETION_TOKEN_FOR(typename detail::spawn_signature<
      result_of_t<F(basic_yield_context<Executor>)>>::type)
        CompletionToken>
inline auto spawn(const Executor& ex, allocator_arg_t,
    StackAllocator&& stack_allocator, F&& function, CompletionToken&& token,
    constraint_t<
      is_executor<Executor>::value || execution::is_executor<Executor>::value
    >)
  -> decltype(
    async_initiate<CompletionToken,
      typename detail::spawn_signature<
        result_of_t<F(basic_yield_context<Executor>)>>::type>(
          declval<detail::initiate_spawn<Executor>>(),
          token, allocator_arg_t(),
          static_cast<StackAllocator&&>(stack_allocator),
          static_cast<F&&>(function)))
{
  return async_initiate<CompletionToken,
    typename detail::spawn_signature<
      result_of_t<F(basic_yield_context<Executor>)>>::type>(
        detail::initiate_spawn<Executor>(ex), token, allocator_arg_t(),
        static_cast<StackAllocator&&>(stack_allocator),
        static_cast<F&&>(function));
}

template <typename ExecutionContext, typename StackAllocator, typename F,
    ASIO_COMPLETION_TOKEN_FOR(typename detail::spawn_signature<
      result_of_t<F(basic_yield_context<
        typename ExecutionContext::executor_type>)>>::type) CompletionToken>
inline auto spawn(ExecutionContext& ctx, allocator_arg_t,
    StackAllocator&& stack_allocator, F&& function, CompletionToken&& token,
    constraint_t<
      is_convertible<ExecutionContext&, execution_context&>::value
    >)
  -> decltype(
    async_initiate<CompletionToken,
      typename detail::spawn_signature<
        result_of_t<F(basic_yield_context<
          typename ExecutionContext::executor_type>)>>::type>(
            declval<detail::initiate_spawn<
              typename ExecutionContext::executor_type>>(),
            token, allocator_arg_t(),
            static_cast<StackAllocator&&>(stack_allocator),
            static_cast<F&&>(function)))
{
  return (spawn)(ctx.get_executor(), allocator_arg_t(),
      static_cast<StackAllocator&&>(stack_allocator),
      static_cast<F&&>(function), static_cast<CompletionToken&&>(token));
}

template <typename Executor, typename StackAllocator, typename F,
    ASIO_COMPLETION_TOKEN_FOR(typename detail::spawn_signature<
      result_of_t<F(basic_yield_context<Executor>)>>::type) CompletionToken>
inline auto spawn(const basic_yield_context<Executor>& ctx, allocator_arg_t,
    StackAllocator&& stack_allocator, F&& function, CompletionToken&& token,
    constraint_t<
      is_executor<Executor>::value || execution::is_executor<Executor>::value
    >)
  -> decltype(
    async_initiate<CompletionToken,
      typename detail::spawn_signature<
        result_of_t<F(basic_yield_context<Executor>)>>::type>(
          declval<detail::initiate_spawn<Executor>>(), token,
          allocator_arg_t(), static_cast<StackAllocator&&>(stack_allocator),
          static_cast<F&&>(function)))
{
  return (spawn)(ctx.get_executor(), allocator_arg_t(),
      static_cast<StackAllocator&&>(stack_allocator),
      static_cast<F&&>(function), static_cast<CompletionToken&&>(token));
}

#endif // defined(ASIO_HAS_BOOST_CONTEXT_FIBER)

#if defined(ASIO_HAS_BOOST_COROUTINE)

namespace detail {

template <typename Executor, typename Function, typename Handler>
class old_spawn_entry_point
{
public:
  template <typename F, typename H>
  old_spawn_entry_point(const Executor& ex, F&& f, H&& h)
    : executor_(ex),
      function_(static_cast<F&&>(f)),
      handler_(static_cast<H&&>(h))
  {
  }

  void operator()(spawned_thread_base* spawned_thread)
  {
    const basic_yield_context<Executor> yield(spawned_thread, executor_);
    this->call(yield,
        void_type<result_of_t<Function(basic_yield_context<Executor>)>>());
  }

private:
  void call(const basic_yield_context<Executor>& yield, void_type<void>)
  {
    function_(yield);
    static_cast<Handler&&>(handler_)();
  }

  template <typename T>
  void call(const basic_yield_context<Executor>& yield, void_type<T>)
  {
    static_cast<Handler&&>(handler_)(function_(yield));
  }

  Executor executor_;
  Function function_;
  Handler handler_;
};

inline void default_spawn_handler() {}

} // namespace detail

template <typename Function>
inline void spawn(Function&& function,
    const boost::coroutines::attributes& attributes)
{
  associated_executor_t<decay_t<Function>> ex(
      (get_associated_executor)(function));

  asio::spawn(ex, static_cast<Function&&>(function), attributes);
}

template <typename Handler, typename Function>
void spawn(Handler&& handler, Function&& function,
    const boost::coroutines::attributes& attributes,
    constraint_t<
      !is_executor<decay_t<Handler>>::value &&
      !execution::is_executor<decay_t<Handler>>::value &&
      !is_convertible<Handler&, execution_context&>::value>)
{
  typedef associated_executor_t<decay_t<Handler>> executor_type;
  executor_type ex((get_associated_executor)(handler));

  (dispatch)(ex,
      detail::spawned_thread_resumer(
        detail::spawned_coroutine_thread::spawn(
          detail::old_spawn_entry_point<executor_type,
            decay_t<Function>, void (*)()>(
              ex, static_cast<Function&&>(function),
              &detail::default_spawn_handler), attributes)));
}

template <typename Executor, typename Function>
void spawn(basic_yield_context<Executor> ctx, Function&& function,
    const boost::coroutines::attributes& attributes)
{
  (dispatch)(ctx.get_executor(),
      detail::spawned_thread_resumer(
        detail::spawned_coroutine_thread::spawn(
          detail::old_spawn_entry_point<Executor,
            decay_t<Function>, void (*)()>(
              ctx.get_executor(), static_cast<Function&&>(function),
              &detail::default_spawn_handler), attributes)));
}

template <typename Function, typename Executor>
inline void spawn(const Executor& ex, Function&& function,
    const boost::coroutines::attributes& attributes,
    constraint_t<
      is_executor<Executor>::value || execution::is_executor<Executor>::value
    >)
{
  asio::spawn(asio::strand<Executor>(ex),
      static_cast<Function&&>(function), attributes);
}

template <typename Function, typename Executor>
inline void spawn(const strand<Executor>& ex, Function&& function,
    const boost::coroutines::attributes& attributes)
{
  asio::spawn(asio::bind_executor(
        ex, &detail::default_spawn_handler),
      static_cast<Function&&>(function), attributes);
}

#if !defined(ASIO_NO_TS_EXECUTORS)

template <typename Function>
inline void spawn(const asio::io_context::strand& s, Function&& function,
    const boost::coroutines::attributes& attributes)
{
  asio::spawn(asio::bind_executor(
        s, &detail::default_spawn_handler),
      static_cast<Function&&>(function), attributes);
}

#endif // !defined(ASIO_NO_TS_EXECUTORS)

template <typename Function, typename ExecutionContext>
inline void spawn(ExecutionContext& ctx, Function&& function,
    const boost::coroutines::attributes& attributes,
    constraint_t<
      is_convertible<ExecutionContext&, execution_context&>::value
    >)
{
  asio::spawn(ctx.get_executor(),
      static_cast<Function&&>(function), attributes);
}

#endif // defined(ASIO_HAS_BOOST_COROUTINE)

} // namespace asio

#include "asio/detail/pop_options.hpp"

#endif // ASIO_IMPL_SPAWN_HPP
