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:
@@ -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)
|
||||
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
50
include/boost/asio/impl/awaitable.ipp
Normal file
50
include/boost/asio/impl/awaitable.ipp
Normal 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
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user