2
0
mirror of https://github.com/boostorg/asio.git synced 2026-01-19 04:02:09 +00:00

Make co_spawn adhere to async op requirement for non-reentrant completion.

Previously, co_spawn-ing the following coroutine:

  awaitable<void> foo()
  {
    co_await dispatch(use_awaitable);
  }

would result in a call to the completion handler from within co_spawn.
This commit is contained in:
Christopher Kohlhoff
2025-08-04 23:19:36 +10:00
parent 419a06e978
commit 7e51960177
6 changed files with 135 additions and 18 deletions

View File

@@ -138,6 +138,9 @@ private:
#include <boost/asio/detail/pop_options.hpp>
#include <boost/asio/impl/awaitable.hpp>
#if defined(BOOST_ASIO_HEADER_ONLY)
# include <boost/asio/impl/awaitable.ipp>
#endif // defined(BOOST_ASIO_HEADER_ONLY)
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) || defined(GENERATING_DOCUMENTATION)

View File

@@ -44,7 +44,6 @@ namespace boost {
namespace asio {
namespace detail {
struct awaitable_thread_has_context_switched {};
template <typename, typename, typename> class awaitable_async_op_handler;
template <typename, typename, typename> class awaitable_async_op;
@@ -84,8 +83,17 @@ template <typename, typename, typename> class awaitable_async_op;
// | |
// +-----------------+
class awaitable_launch_context
{
public:
BOOST_ASIO_DECL void launch(void (*pump_fn)(void*), void* arg);
BOOST_ASIO_DECL bool is_launching();
};
struct awaitable_thread_is_launching {};
template <typename Executor>
class awaitable_frame_base
class awaitable_frame_base : public awaitable_launch_context
{
public:
#if !defined(BOOST_ASIO_DISABLE_AWAITABLE_FRAME_RECYCLING)
@@ -433,8 +441,8 @@ public:
return result{std::move(f), this};
}
// Access the awaitable thread's has_context_switched_ flag.
auto await_transform(detail::awaitable_thread_has_context_switched) noexcept
// Determine whether the awaitable thread is launching.
auto await_transform(detail::awaitable_thread_is_launching) noexcept
{
struct result
{
@@ -449,9 +457,9 @@ public:
{
}
bool& await_resume() const noexcept
bool await_resume() const noexcept
{
return this_->attached_thread_->entry_point()->has_context_switched_;
return this_->is_launching();
}
};
@@ -465,7 +473,6 @@ public:
awaitable_thread<Executor>* detach_thread() noexcept
{
attached_thread_->entry_point()->has_context_switched_ = true;
return std::exchange(attached_thread_, nullptr);
}
@@ -604,7 +611,6 @@ public:
awaitable_frame()
: top_of_stack_(0),
has_executor_(false),
has_context_switched_(false),
throw_if_cancelled_(true)
{
}
@@ -650,7 +656,6 @@ private:
boost::asio::cancellation_slot parent_cancellation_slot_;
boost::asio::cancellation_state cancellation_state_;
bool has_executor_;
bool has_context_switched_;
bool throw_if_cancelled_;
};
@@ -755,7 +760,7 @@ public:
void launch()
{
bottom_of_stack_.frame_->top_of_stack_->attach_thread(this);
pump();
bottom_of_stack_.frame_->launch(&awaitable_thread::do_pump, this);
}
protected:
@@ -777,6 +782,11 @@ protected:
}
}
static void do_pump(void* self)
{
static_cast<awaitable_thread*>(self)->pump();
}
awaitable<awaitable_thread_entry_point, Executor> bottom_of_stack_;
};

View File

@@ -0,0 +1,50 @@
//
// impl/awaitable.ipp
// ~~~~~~~~~~~~~~~~~~
//
// 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 BOOST_ASIO_IMPL_AWAITABLE_IPP
#define BOOST_ASIO_IMPL_AWAITABLE_IPP
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
#include <boost/asio/detail/config.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/awaitable.hpp>
#include <boost/asio/detail/call_stack.hpp>
#include <boost/asio/detail/push_options.hpp>
namespace boost {
namespace asio {
namespace detail {
void awaitable_launch_context::launch(void (*pump_fn)(void*), void* arg)
{
call_stack<awaitable_launch_context>::context ctx(this);
pump_fn(arg);
}
bool awaitable_launch_context::is_launching()
{
return !!call_stack<awaitable_launch_context>::contains(this);
}
} // namespace detail
} // namespace asio
} // namespace boost
#include <boost/asio/detail/pop_options.hpp>
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#endif // BOOST_ASIO_IMPL_AWAITABLE_IPP

View File

@@ -140,7 +140,6 @@ awaitable<awaitable_thread_entry_point, Executor> co_spawn_entry_point(
{
(void) co_await co_spawn_dispatch{};
(co_await awaitable_thread_has_context_switched{}) = false;
std::exception_ptr e = nullptr;
bool done = false;
#if !defined(BOOST_ASIO_NO_EXCEPTIONS)
@@ -151,8 +150,8 @@ awaitable<awaitable_thread_entry_point, Executor> co_spawn_entry_point(
done = true;
bool switched = (co_await awaitable_thread_has_context_switched{});
if (!switched)
bool is_launching = (co_await awaitable_thread_is_launching{});
if (is_launching)
{
co_await this_coro::throw_if_cancelled(false);
(void) co_await co_spawn_post();
@@ -176,8 +175,8 @@ awaitable<awaitable_thread_entry_point, Executor> co_spawn_entry_point(
}
#endif // !defined(BOOST_ASIO_NO_EXCEPTIONS)
bool switched = (co_await awaitable_thread_has_context_switched{});
if (!switched)
bool is_launching = (co_await awaitable_thread_is_launching{});
if (is_launching)
{
co_await this_coro::throw_if_cancelled(false);
(void) co_await co_spawn_post();
@@ -196,7 +195,6 @@ awaitable<awaitable_thread_entry_point, Executor> co_spawn_entry_point(
{
(void) co_await co_spawn_dispatch{};
(co_await awaitable_thread_has_context_switched{}) = false;
std::exception_ptr e = nullptr;
#if !defined(BOOST_ASIO_NO_EXCEPTIONS)
try
@@ -211,8 +209,8 @@ awaitable<awaitable_thread_entry_point, Executor> co_spawn_entry_point(
}
#endif // !defined(BOOST_ASIO_NO_EXCEPTIONS)
bool switched = (co_await awaitable_thread_has_context_switched{});
if (!switched)
bool is_launching = (co_await awaitable_thread_is_launching{});
if (is_launching)
{
co_await this_coro::throw_if_cancelled(false);
(void) co_await co_spawn_post();

View File

@@ -21,6 +21,7 @@
#include <boost/asio/impl/any_completion_executor.ipp>
#include <boost/asio/impl/any_io_executor.ipp>
#include <boost/asio/impl/awaitable.ipp>
#include <boost/asio/impl/cancellation_signal.ipp>
#include <boost/asio/impl/config.ipp>
#include <boost/asio/impl/connect_pipe.ipp>

View File

@@ -23,7 +23,9 @@
#include <stdexcept>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/use_awaitable.hpp>
boost::asio::awaitable<void> void_returning_coroutine()
{
@@ -115,11 +117,64 @@ void test_co_spawn_immediate_cancel()
BOOST_ASIO_CHECK(result != nullptr);
}
boost::asio::awaitable<void> void_returning_dispatch_coroutine()
{
co_await boost::asio::dispatch(boost::asio::use_awaitable);
co_return;
}
boost::asio::awaitable<int> int_returning_dispatch_coroutine()
{
co_await boost::asio::dispatch(boost::asio::use_awaitable);
co_return 42;
}
void test_co_spawn_with_immediate_completion_via_dispatch()
{
boost::asio::io_context ctx;
bool called = false;
boost::asio::post(ctx,
[&]
{
boost::asio::co_spawn(ctx, void_returning_dispatch_coroutine(),
[&](std::exception_ptr)
{
called = true;
});
BOOST_ASIO_CHECK(!called);
});
ctx.run();
BOOST_ASIO_CHECK(called);
int result = 0;
boost::asio::post(ctx,
[&]
{
boost::asio::co_spawn(ctx, int_returning_dispatch_coroutine(),
[&](std::exception_ptr, int i)
{
result = i;
});
BOOST_ASIO_CHECK(result == 0);
});
ctx.restart();
ctx.run();
BOOST_ASIO_CHECK(result == 42);
}
BOOST_ASIO_TEST_SUITE
(
"co_spawn",
BOOST_ASIO_TEST_CASE(test_co_spawn_with_any_completion_handler)
BOOST_ASIO_TEST_CASE(test_co_spawn_immediate_cancel)
BOOST_ASIO_TEST_CASE(test_co_spawn_with_immediate_completion_via_dispatch)
)
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)