删减无关内容
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
//
|
||||
#pragma once
|
||||
#ifdef DISABLE_EXCEPTION
|
||||
#error "DISABLE_EXCEPTION is not supported by ucoro currently"
|
||||
#endif
|
||||
#include <any>
|
||||
#include <concepts>
|
||||
#include <cassert>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
@@ -29,10 +27,10 @@ namespace std {
|
||||
using std::experimental::noop_coroutine;
|
||||
using std::experimental::suspend_always;
|
||||
using std::experimental::suspend_never;
|
||||
} // namespace std
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
#error "Compiler version too low to support coroutine !!!"
|
||||
#error "Compiler version too low to support coroutine"
|
||||
#endif
|
||||
#if defined(DEBUG) || defined(_DEBUG)
|
||||
#if defined(ENABLE_DEBUG_CORO_LEAK)
|
||||
@@ -41,7 +39,7 @@ namespace std {
|
||||
inline std::unordered_set<void*> debug_coro_leak;
|
||||
#endif
|
||||
#endif
|
||||
namespace ucoro {
|
||||
namespace psco {
|
||||
template <typename T>
|
||||
struct await_transformer {
|
||||
};
|
||||
@@ -51,19 +49,7 @@ namespace ucoro {
|
||||
struct awaitable_promise;
|
||||
template <typename T, typename CallbackFunction>
|
||||
struct Callback_Awaiter;
|
||||
template <typename T>
|
||||
struct local_storage_t {
|
||||
};
|
||||
inline constexpr local_storage_t<void> local_storage;
|
||||
namespace concepts {
|
||||
template <typename T>
|
||||
struct local_storage_type_impl : std::false_type {
|
||||
};
|
||||
template <typename T>
|
||||
struct local_storage_type_impl<local_storage_t<T>> : std::true_type {
|
||||
};
|
||||
template <typename T>
|
||||
inline constexpr bool local_storage_type = local_storage_type_impl<std::decay_t<T>>::value;
|
||||
template <typename T>
|
||||
struct awaitable_type_impl : std::false_type {
|
||||
};
|
||||
@@ -71,7 +57,7 @@ namespace ucoro {
|
||||
struct awaitable_type_impl<awaitable<T>> : std::true_type {
|
||||
};
|
||||
template <typename T>
|
||||
inline constexpr bool awaitable_type = awaitable_type_impl<std::decay_t<T>>::value;
|
||||
concept awaitable_type = awaitable_type_impl<std::decay_t<T>>::value;
|
||||
template <typename T>
|
||||
struct awaitable_promise_type_impl : std::false_type {
|
||||
};
|
||||
@@ -79,75 +65,29 @@ namespace ucoro {
|
||||
struct awaitable_promise_type_impl<awaitable_promise<T>> : std::true_type {
|
||||
};
|
||||
template <typename T>
|
||||
inline constexpr bool awaitable_promise_type = awaitable_promise_type_impl<std::decay_t<T>>::value;
|
||||
concept awaitable_promise_type = awaitable_promise_type_impl<std::decay_t<T>>::value;
|
||||
template <typename T>
|
||||
inline constexpr bool is_valid_await_suspend_return_value =
|
||||
std::is_convertible_v<T, std::coroutine_handle<>> || std::is_void_v<T> || std::is_same_v<T, bool>;
|
||||
template <typename T, typename = void>
|
||||
struct is_awaiter_impl : std::false_type {
|
||||
concept valid_await_suspend_return_value = std::convertible_to<T, std::coroutine_handle<>> || std::is_void_v<T> || std::same_as<T, bool>;
|
||||
template <typename T>
|
||||
concept awaiter = requires(T a) {
|
||||
{ a.await_ready() } -> std::same_as<bool>;
|
||||
{ a.await_suspend(std::coroutine_handle<>{}) } -> valid_await_suspend_return_value;
|
||||
a.await_resume();
|
||||
};
|
||||
template <typename T>
|
||||
struct is_awaiter_impl<T, std::void_t<
|
||||
decltype(std::declval<T&>().await_ready()),
|
||||
decltype(std::declval<T&>().await_suspend(std::coroutine_handle<>{})),
|
||||
decltype(std::declval<T&>().await_resume())>>
|
||||
: std::bool_constant<
|
||||
std::is_same_v<decltype(std::declval<T&>().await_ready()), bool> &&
|
||||
is_valid_await_suspend_return_value<decltype(std::declval<T&>().await_suspend(std::coroutine_handle<>{})
|
||||
)>> {
|
||||
};
|
||||
// MSVC 2019 and some IDE parsers can fail to evaluate the generic SFINAE
|
||||
// check below for ucoro core awaiters. These explicit specializations keep
|
||||
// the library traits stable without changing runtime behavior.
|
||||
template <typename T>
|
||||
struct is_awaiter_impl<awaitable<T>, void> : std::true_type {
|
||||
};
|
||||
template <typename T, typename CallbackFunction>
|
||||
struct is_awaiter_impl<Callback_Awaiter<T, CallbackFunction>, void> : std::true_type {
|
||||
concept has_operator_co_await = requires(T a) {
|
||||
{ a.operator co_await() } -> awaiter;
|
||||
};
|
||||
template <typename T>
|
||||
inline constexpr bool is_awaiter_v = is_awaiter_impl<std::decay_t<T>>::value;
|
||||
template <typename T, typename = void>
|
||||
struct has_operator_co_await_impl : std::false_type {
|
||||
concept awaitable_value = awaiter<std::decay_t<T>> || awaitable_type<T> || has_operator_co_await<std::decay_t<T>>;
|
||||
template <typename T>
|
||||
concept has_user_defined_await_transformer = requires(T&& a) {
|
||||
await_transformer<std::decay_t<T>>::await_transform(std::move(a));
|
||||
};
|
||||
template <typename T>
|
||||
struct has_operator_co_await_impl<T, std::void_t<decltype(std::declval<T&>().operator co_await())>>
|
||||
: std::bool_constant<is_awaiter_v<decltype(std::declval<T&>().operator co_await())>> {
|
||||
};
|
||||
template <typename T>
|
||||
inline constexpr bool has_operator_co_await = has_operator_co_await_impl<std::decay_t<T>>::value;
|
||||
template <typename T>
|
||||
inline constexpr bool is_awaitable_v =
|
||||
is_awaiter_v<std::decay_t<T>> || awaitable_type<T> || has_operator_co_await<std::decay_t<T>>;
|
||||
template <typename T, typename = void>
|
||||
struct has_user_defined_await_transformer_impl : std::false_type {
|
||||
};
|
||||
template <typename T>
|
||||
struct has_user_defined_await_transformer_impl<T, std::void_t<
|
||||
decltype(await_transformer<std::decay_t<T>>::await_transform(
|
||||
std::declval<T>()))>> : std::true_type {
|
||||
};
|
||||
template <typename T>
|
||||
inline constexpr bool has_user_defined_await_transformer =
|
||||
has_user_defined_await_transformer_impl<T>::value;
|
||||
template <typename T>
|
||||
struct is_not_awaitable : std::false_type {
|
||||
};
|
||||
} // namespace concepts
|
||||
template <typename T, typename R>
|
||||
concept completion_handler_for = std::invocable<T, R>;
|
||||
}
|
||||
namespace traits {
|
||||
template <typename Testee, template<typename> typename FromTemplate>
|
||||
struct template_parameter_traits;
|
||||
template <template<typename> typename ClassTemplate, typename TemplateParameter>
|
||||
struct template_parameter_traits<ClassTemplate<TemplateParameter>, ClassTemplate> {
|
||||
using template_parameter = TemplateParameter;
|
||||
};
|
||||
template <typename TesteeType, template<typename> typename FromTemplate>
|
||||
using template_parameter_of = typename template_parameter_traits<
|
||||
std::decay_t<TesteeType>, FromTemplate>::template_parameter;
|
||||
template <typename LocalStorage>
|
||||
using local_storage_value_type = template_parameter_of<LocalStorage, local_storage_t>;
|
||||
template <typename AwaitableType>
|
||||
using awaitable_return_type = template_parameter_of<AwaitableType, awaitable>;
|
||||
template <typename T>
|
||||
struct exception_with_result {
|
||||
using type = std::variant<std::exception_ptr, T>;
|
||||
@@ -158,7 +98,7 @@ namespace ucoro {
|
||||
};
|
||||
template <typename T>
|
||||
using exception_with_result_t = typename exception_with_result<T>::type;
|
||||
} // namespace traits
|
||||
}
|
||||
struct debug_coro_promise {
|
||||
#if defined(DEBUG_CORO_PROMISE_LEAK)
|
||||
void* operator new(std::size_t size) {
|
||||
@@ -186,11 +126,14 @@ namespace ucoro {
|
||||
}
|
||||
T get_value() {
|
||||
if (std::holds_alternative<std::exception_ptr>(value_)) {
|
||||
std::rethrow_exception(std::get<std::exception_ptr>(value_));
|
||||
auto exception = std::get<std::exception_ptr>(value_);
|
||||
if (exception) {
|
||||
std::rethrow_exception(exception);
|
||||
}
|
||||
}
|
||||
return std::move(std::get<T>(value_));
|
||||
}
|
||||
std::variant<std::exception_ptr, T> value_{std::exception_ptr{}};
|
||||
std::variant<std::exception_ptr, T> value_{std::in_place_index<0>, std::exception_ptr{}};
|
||||
};
|
||||
template <>
|
||||
struct awaitable_promise_value<void> {
|
||||
@@ -220,10 +163,6 @@ namespace ucoro {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return static_cast<bool>(handle_) && !completed_;
|
||||
}
|
||||
[[nodiscard]] bool has_handle() const noexcept {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return static_cast<bool>(handle_);
|
||||
}
|
||||
[[nodiscard]] bool started() const noexcept {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return started_;
|
||||
@@ -240,7 +179,7 @@ namespace ucoro {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
started_ = true;
|
||||
}
|
||||
void request_abandon() noexcept {
|
||||
void cancel() noexcept {
|
||||
std::shared_ptr<coroutine_control_block> child;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
@@ -248,7 +187,7 @@ namespace ucoro {
|
||||
child = child_.lock();
|
||||
}
|
||||
if (child) {
|
||||
child->request_abandon();
|
||||
child->cancel();
|
||||
}
|
||||
}
|
||||
void set_child(const std::shared_ptr<coroutine_control_block>& child) noexcept {
|
||||
@@ -259,7 +198,7 @@ namespace ucoro {
|
||||
cancel_child = cancel_requested_;
|
||||
}
|
||||
if (cancel_child && child) {
|
||||
child->request_abandon();
|
||||
child->cancel();
|
||||
}
|
||||
}
|
||||
void start() noexcept {
|
||||
@@ -342,20 +281,13 @@ namespace ucoro {
|
||||
}
|
||||
}
|
||||
else {
|
||||
// The coroutine is already running and may be suspended inside an
|
||||
// external callback. Do not destroy the frame here. Mark it as
|
||||
// cancelled and let the callback resume it once so it can unwind to
|
||||
// final_suspend(), where the frame is destroyed safely.
|
||||
cancel_requested_ = true;
|
||||
destroy_on_completion_ = true;
|
||||
child = child_.lock();
|
||||
}
|
||||
}
|
||||
if (child) {
|
||||
child->request_abandon();
|
||||
}
|
||||
if (!handle) {
|
||||
return;
|
||||
child->cancel();
|
||||
}
|
||||
if (handle) {
|
||||
handle.destroy();
|
||||
@@ -364,7 +296,7 @@ namespace ucoro {
|
||||
[[nodiscard]] bool complete_in_final_suspend() noexcept {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
completed_ = true;
|
||||
const bool needs_deferred_finish = cancel_requested_ || destroy_on_completion_ || resume_in_progress_;
|
||||
bool needs_deferred_finish = cancel_requested_ || destroy_on_completion_ || resume_in_progress_;
|
||||
resume_in_progress_ = needs_deferred_finish;
|
||||
return needs_deferred_finish;
|
||||
}
|
||||
@@ -395,9 +327,7 @@ namespace ucoro {
|
||||
struct final_resume_task {
|
||||
struct promise_type {
|
||||
final_resume_task get_return_object() noexcept {
|
||||
return final_resume_task{
|
||||
std::coroutine_handle<promise_type>::from_promise(*this)
|
||||
};
|
||||
return final_resume_task{std::coroutine_handle<promise_type>::from_promise(*this)};
|
||||
}
|
||||
std::suspend_always initial_suspend() noexcept {
|
||||
return {};
|
||||
@@ -466,67 +396,40 @@ namespace ucoro {
|
||||
auto final_suspend() noexcept {
|
||||
return final_awaitable<T>{this};
|
||||
}
|
||||
auto initial_suspend() {
|
||||
auto initial_suspend() noexcept {
|
||||
return std::suspend_always{};
|
||||
}
|
||||
void set_local(std::any local) {
|
||||
local_ = std::make_shared<std::any>(std::move(local));
|
||||
}
|
||||
template <typename localtype>
|
||||
struct local_storage_awaiter {
|
||||
const awaitable_promise* this_;
|
||||
[[nodiscard]] constexpr bool await_ready() const noexcept { return true; }
|
||||
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {
|
||||
}
|
||||
auto await_resume() const {
|
||||
if (!this_->local_) {
|
||||
throw std::logic_error("ucoro local_storage is not set");
|
||||
}
|
||||
if constexpr (std::is_void_v<localtype>) {
|
||||
return *this_->local_;
|
||||
}
|
||||
else {
|
||||
return std::any_cast<localtype>(*this_->local_);
|
||||
}
|
||||
}
|
||||
};
|
||||
template <typename A>
|
||||
auto await_transform(A&& awaiter) const {
|
||||
if constexpr (concepts::local_storage_type<std::decay_t<A>>) {
|
||||
return local_storage_awaiter<traits::local_storage_value_type<std::decay_t<A>>>{this};
|
||||
if constexpr (concepts::has_user_defined_await_transformer<A>) {
|
||||
return await_transformer<std::decay_t<A>>::await_transform(std::forward<A>(awaiter));
|
||||
}
|
||||
else if constexpr (concepts::has_user_defined_await_transformer<A>) {
|
||||
return await_transformer<std::decay_t<A>>::await_transform(std::move(awaiter));
|
||||
}
|
||||
else if constexpr (concepts::is_awaitable_v<A>) {
|
||||
static_assert(std::is_rvalue_reference_v<A&&>, "co_await must be used on rvalue");
|
||||
else if constexpr (concepts::awaitable_value<A>) {
|
||||
static_assert(std::is_rvalue_reference_v<A&&>, "co_await must use an rvalue awaitable");
|
||||
return std::forward<A>(awaiter);
|
||||
}
|
||||
else {
|
||||
static_assert(concepts::is_not_awaitable<A>::value, "co_await must be called on an awaitable type");
|
||||
static_assert(!std::is_same_v<A, A>, "co_await expression is not awaitable");
|
||||
}
|
||||
}
|
||||
std::coroutine_handle<> parent_{};
|
||||
std::shared_ptr<coroutine_control_block> parent_control_{};
|
||||
std::shared_ptr<coroutine_control_block> control_{std::make_shared<coroutine_control_block>()};
|
||||
std::shared_ptr<std::any> local_{};
|
||||
};
|
||||
template <typename T>
|
||||
struct awaitable {
|
||||
using promise_type = awaitable_promise<T>;
|
||||
explicit awaitable(std::coroutine_handle<promise_type> h)
|
||||
: control_(h.promise().control_) {
|
||||
explicit awaitable(std::coroutine_handle<promise_type> h) : control_(h.promise().control_) {
|
||||
}
|
||||
~awaitable() noexcept {
|
||||
reset();
|
||||
}
|
||||
awaitable(awaitable&& t) noexcept
|
||||
: control_(std::move(t.control_)) {
|
||||
awaitable(awaitable&& other) noexcept : control_(std::move(other.control_)) {
|
||||
}
|
||||
awaitable& operator=(awaitable&& t) noexcept {
|
||||
if (&t != this) {
|
||||
awaitable& operator=(awaitable&& other) noexcept {
|
||||
if (&other != this) {
|
||||
reset();
|
||||
control_ = std::move(t.control_);
|
||||
control_ = std::move(other.control_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -548,9 +451,6 @@ namespace ucoro {
|
||||
auto await_suspend(std::coroutine_handle<PromiseType> continuation) {
|
||||
auto handle = typed_handle();
|
||||
if constexpr (concepts::awaitable_promise_type<PromiseType>) {
|
||||
handle.promise().local_ = handle.promise().local_
|
||||
? handle.promise().local_
|
||||
: continuation.promise().local_;
|
||||
handle.promise().parent_control_ = continuation.promise().control_;
|
||||
continuation.promise().control_->set_child(control_);
|
||||
}
|
||||
@@ -563,13 +463,10 @@ namespace ucoro {
|
||||
[[nodiscard]] bool valid() const noexcept {
|
||||
return control_ && control_->valid();
|
||||
}
|
||||
void request_abandon() noexcept {
|
||||
if (control_) {
|
||||
control_->request_abandon();
|
||||
}
|
||||
}
|
||||
void cancel() noexcept {
|
||||
request_abandon();
|
||||
if (control_) {
|
||||
control_->cancel();
|
||||
}
|
||||
}
|
||||
void reset() noexcept {
|
||||
if (control_) {
|
||||
@@ -588,56 +485,12 @@ namespace ucoro {
|
||||
control_.reset();
|
||||
}
|
||||
}
|
||||
void set_local(std::any local) {
|
||||
auto handle = typed_handle();
|
||||
assert("local has value" && !handle.promise().local_);
|
||||
handle.promise().set_local(std::move(local));
|
||||
}
|
||||
auto detach(std::any local = {}) {
|
||||
auto launched_coro = [](awaitable<T> lazy) mutable -> awaitable<T> {
|
||||
co_return co_await std::move(lazy);
|
||||
}(std::move(*this));
|
||||
if (local.has_value()) {
|
||||
launched_coro.set_local(local);
|
||||
}
|
||||
return launched_coro;
|
||||
}
|
||||
template <typename Function, typename = std::enable_if_t<std::is_invocable_v<
|
||||
Function, ucoro::traits::exception_with_result_t<T>>>>
|
||||
auto detach_with_callback(Function completion_handler) {
|
||||
return detach_with_callback<Function>(std::any{}, std::move(completion_handler));
|
||||
}
|
||||
template <typename Function, typename = std::enable_if_t<std::is_invocable_v<
|
||||
Function, ucoro::traits::exception_with_result_t<T>>>>
|
||||
auto detach_with_callback(std::any local, Function completion_handler) {
|
||||
auto launched_coro = [](awaitable<T> lazy, auto completion_handler) mutable -> awaitable<void> {
|
||||
using result_wrapper = ucoro::traits::exception_with_result_t<T>;
|
||||
result_wrapper result{};
|
||||
try {
|
||||
if constexpr (std::is_void_v<T>) {
|
||||
co_await std::move(lazy);
|
||||
result = nullptr;
|
||||
}
|
||||
else {
|
||||
result = result_wrapper{co_await std::move(lazy)};
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
result = result_wrapper{std::current_exception()};
|
||||
}
|
||||
completion_handler(std::move(result));
|
||||
}(std::move(*this), std::move(completion_handler));
|
||||
if (local.has_value()) {
|
||||
launched_coro.set_local(local);
|
||||
}
|
||||
return launched_coro;
|
||||
}
|
||||
std::shared_ptr<coroutine_control_block> control_;
|
||||
private:
|
||||
[[nodiscard]] std::coroutine_handle<promise_type> typed_handle() const noexcept {
|
||||
assert(control_ && "awaitable has no coroutine control block");
|
||||
assert(control_);
|
||||
auto handle = control_->handle();
|
||||
assert(handle && "awaitable has no coroutine handle");
|
||||
assert(handle);
|
||||
return std::coroutine_handle<promise_type>::from_address(handle.address());
|
||||
}
|
||||
};
|
||||
@@ -647,8 +500,6 @@ namespace ucoro {
|
||||
control_->attach(handle);
|
||||
return awaitable<T>{handle};
|
||||
}
|
||||
} // namespace ucoro
|
||||
namespace ucoro {
|
||||
template <typename T>
|
||||
struct Callback_Awaiter_State {
|
||||
std::mutex mutex_;
|
||||
@@ -670,16 +521,14 @@ namespace ucoro {
|
||||
};
|
||||
template <typename T, typename CallbackFunction>
|
||||
struct Callback_Awaiter {
|
||||
explicit Callback_Awaiter(CallbackFunction&& callback_function) : callback_function_(std::forward<CallbackFunction>(callback_function)) {
|
||||
}
|
||||
Callback_Awaiter(const Callback_Awaiter&) = delete;
|
||||
Callback_Awaiter& operator=(const Callback_Awaiter&) = delete;
|
||||
public:
|
||||
explicit Callback_Awaiter(CallbackFunction&& callback_function)
|
||||
: callback_function_(std::forward<CallbackFunction>(callback_function)) {
|
||||
}
|
||||
Callback_Awaiter(Callback_Awaiter&&) noexcept = default;
|
||||
Callback_Awaiter& operator=(Callback_Awaiter&&) noexcept = default;
|
||||
~Callback_Awaiter() {
|
||||
cancel_state();
|
||||
cancel_state(state_);
|
||||
}
|
||||
constexpr bool await_ready() noexcept {
|
||||
return false;
|
||||
@@ -707,8 +556,8 @@ namespace ucoro {
|
||||
});
|
||||
}
|
||||
else {
|
||||
callback_function_([state](T t) mutable {
|
||||
complete_state(state, std::move(t));
|
||||
callback_function_([state](T value) mutable {
|
||||
complete_state(state, std::move(value));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -718,12 +567,7 @@ namespace ucoro {
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state->mutex_);
|
||||
if (state->completed_) {
|
||||
// The callback completed before await_suspend returned. The coroutine
|
||||
// must not suspend; await_resume() will consume the stored result.
|
||||
return false;
|
||||
}
|
||||
if (state->cancelled_) {
|
||||
if (state->completed_ || state->cancelled_) {
|
||||
return false;
|
||||
}
|
||||
state->await_suspend_finished_ = true;
|
||||
@@ -731,7 +575,7 @@ namespace ucoro {
|
||||
return true;
|
||||
}
|
||||
T await_resume() {
|
||||
assert(state_ && "callback awaiter has no state");
|
||||
assert(state_);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_->mutex_);
|
||||
if (state_->cancelled_) {
|
||||
@@ -747,21 +591,17 @@ namespace ucoro {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
assert(state_->result_.has_value() && "callback result was not set before await_resume");
|
||||
assert(state_->result_.has_value());
|
||||
return std::move(*state_->result_);
|
||||
}
|
||||
}
|
||||
private:
|
||||
using State = Callback_Awaiter_State<T>;
|
||||
static void cancel_state(const std::shared_ptr<State>& state) noexcept {
|
||||
if (!state) {
|
||||
return;
|
||||
if (state) {
|
||||
std::lock_guard<std::mutex> lock(state->mutex_);
|
||||
state->cancelled_ = true;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(state->mutex_);
|
||||
state->cancelled_ = true;
|
||||
}
|
||||
void cancel_state() noexcept {
|
||||
cancel_state(state_);
|
||||
}
|
||||
static void resume_state(const std::shared_ptr<State>& state) {
|
||||
std::shared_ptr<coroutine_control_block> owner;
|
||||
@@ -785,12 +625,10 @@ namespace ucoro {
|
||||
}
|
||||
template <typename V>
|
||||
static void complete_state(const std::shared_ptr<State>& state, V&& value) {
|
||||
const bool owner_cancelled = [&]() noexcept {
|
||||
if (auto owner = state->owner_.lock()) {
|
||||
return owner->cancel_requested();
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
bool owner_cancelled = false;
|
||||
if (auto owner = state->owner_.lock()) {
|
||||
owner_cancelled = owner->cancel_requested();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state->mutex_);
|
||||
if (state->completed_ || state->cancelled_) {
|
||||
@@ -807,12 +645,10 @@ namespace ucoro {
|
||||
resume_state(state);
|
||||
}
|
||||
static void complete_state(const std::shared_ptr<State>& state) {
|
||||
const bool owner_cancelled = [&]() noexcept {
|
||||
if (auto owner = state->owner_.lock()) {
|
||||
return owner->cancel_requested();
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
bool owner_cancelled = false;
|
||||
if (auto owner = state->owner_.lock()) {
|
||||
owner_cancelled = owner->cancel_requested();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state->mutex_);
|
||||
if (state->completed_ || state->cancelled_) {
|
||||
@@ -828,71 +664,55 @@ namespace ucoro {
|
||||
CallbackFunction callback_function_;
|
||||
std::shared_ptr<State> state_;
|
||||
};
|
||||
template <typename T, typename callback>
|
||||
[[nodiscard]] auto callback_awaitable(callback&& cb) -> awaitable<T> {
|
||||
co_return co_await Callback_Awaiter<T, callback>{std::forward<callback>(cb)};
|
||||
template <typename T, typename Callback>
|
||||
[[nodiscard]] awaitable<T> callback_awaitable(Callback&& callback) {
|
||||
co_return co_await Callback_Awaiter<T, Callback>{std::forward<Callback>(callback)};
|
||||
}
|
||||
template <typename Awaitable, typename Local, typename CompleteFunction>
|
||||
[[nodiscard]] auto coro_start(Awaitable&& coro, Local&& local, CompleteFunction completer) {
|
||||
auto launched_coro = coro.detach_with_callback(std::forward<Local>(local), std::move(completer));
|
||||
launched_coro.start();
|
||||
return launched_coro;
|
||||
template <typename T, typename CompleteFunction>
|
||||
requires concepts::completion_handler_for<CompleteFunction, traits::exception_with_result_t<T>>
|
||||
[[nodiscard]] awaitable<void> with_callback(awaitable<T> task, CompleteFunction completion_handler) {
|
||||
using result_type = traits::exception_with_result_t<T>;
|
||||
result_type result{};
|
||||
try {
|
||||
if constexpr (std::is_void_v<T>) {
|
||||
co_await std::move(task);
|
||||
result = nullptr;
|
||||
}
|
||||
else {
|
||||
result.template emplace<T>(co_await std::move(task));
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
if constexpr (std::is_void_v<T>) {
|
||||
result = std::current_exception();
|
||||
}
|
||||
else {
|
||||
result.template emplace<std::exception_ptr>(std::current_exception());
|
||||
}
|
||||
}
|
||||
completion_handler(std::move(result));
|
||||
co_return;
|
||||
}
|
||||
template <typename Awaitable, typename Local>
|
||||
[[nodiscard]] auto coro_start(Awaitable&& coro, Local&& local) {
|
||||
auto launched_coro = coro.detach(std::forward<Local>(local));
|
||||
launched_coro.start();
|
||||
return launched_coro;
|
||||
}
|
||||
template <typename Awaitable>
|
||||
[[nodiscard]] auto coro_start(Awaitable&& coro) {
|
||||
auto launched_coro = coro.detach();
|
||||
launched_coro.start();
|
||||
return launched_coro;
|
||||
}
|
||||
template <typename Awaitable, typename Local, typename CompleteFunction>
|
||||
void start_detached(Awaitable&& coro, Local&& local, CompleteFunction completer) {
|
||||
auto launched_coro = coro.detach_with_callback(std::forward<Local>(local), std::move(completer));
|
||||
launched_coro.start_detached();
|
||||
}
|
||||
template <typename Awaitable, typename Local>
|
||||
void start_detached(Awaitable&& coro, Local&& local) {
|
||||
auto launched_coro = coro.detach(std::forward<Local>(local));
|
||||
launched_coro.start_detached();
|
||||
}
|
||||
template <typename Awaitable>
|
||||
void start_detached(Awaitable&& coro) {
|
||||
auto launched_coro = coro.detach();
|
||||
launched_coro.start_detached();
|
||||
}
|
||||
// Synchronously waits until the ucoro task completes and returns its result.
|
||||
//
|
||||
// Usage:
|
||||
// int value = ucoro::sync_await(make_ucoro_task());
|
||||
// int value = ucoro::sync_await(make_ucoro_task(), std::string{"request-local"});
|
||||
//
|
||||
// This function blocks the current thread with a condition_variable. It is valid for
|
||||
// tasks whose completion callback is invoked from another thread, or for tasks that
|
||||
// finish synchronously. It does not run an event loop. Do not use it to wait for work
|
||||
// that requires the current thread to pump asio, drogon, Qt, libuv, or another loop.
|
||||
template <typename T>
|
||||
auto sync_await(awaitable<T> lazy, std::any local_ = {}) -> T {
|
||||
std::mutex mtx;
|
||||
auto sync_await(awaitable<T> task) -> T {
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
bool done = false;
|
||||
traits::exception_with_result_t<T> result;
|
||||
auto launched_coro = lazy.detach_with_callback(local_, [&](traits::exception_with_result_t<T> result_) mutable {
|
||||
traits::exception_with_result_t<T> result{};
|
||||
auto launched = with_callback(std::move(task), [&](traits::exception_with_result_t<T> value) mutable {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
result = std::move(result_);
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
result = std::move(value);
|
||||
done = true;
|
||||
}
|
||||
cv.notify_one();
|
||||
});
|
||||
launched_coro.start();
|
||||
launched.start();
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
cv.wait(lock, [&] { return done; });
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
cv.wait(lock, [&] {
|
||||
return done;
|
||||
});
|
||||
}
|
||||
if constexpr (std::is_void_v<T>) {
|
||||
if (result) {
|
||||
@@ -902,9 +722,12 @@ namespace ucoro {
|
||||
}
|
||||
else {
|
||||
if (std::holds_alternative<std::exception_ptr>(result)) {
|
||||
std::rethrow_exception(std::get<std::exception_ptr>(result));
|
||||
auto exception = std::get<std::exception_ptr>(result);
|
||||
if (exception) {
|
||||
std::rethrow_exception(exception);
|
||||
}
|
||||
}
|
||||
return std::move(std::get<T>(result));
|
||||
}
|
||||
}
|
||||
} // namespace ucoro
|
||||
}
|
||||
@@ -8,10 +8,10 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "awaitable.hpp"
|
||||
namespace ucoro {
|
||||
namespace psco {
|
||||
class Single_Thread_Scheduler {
|
||||
private:
|
||||
using Abandoned_Task = ucoro::awaitable<void>;
|
||||
using Abandoned_Task = psco::awaitable<void>;
|
||||
public:
|
||||
Single_Thread_Scheduler();
|
||||
~Single_Thread_Scheduler();
|
||||
@@ -71,7 +71,7 @@ namespace ucoro {
|
||||
cleanup_abandoned_tasks_locked(garbage);
|
||||
for (auto it = tasks.begin(); it != tasks.end();) {
|
||||
if (it->second.valid()) {
|
||||
it->second.request_abandon();
|
||||
it->second.cancel();
|
||||
abandoned_tasks_.emplace_back(std::move(it->second));
|
||||
}
|
||||
it = tasks.erase(it);
|
||||
@@ -81,4 +81,4 @@ namespace ucoro {
|
||||
}
|
||||
cv_.notify_one();
|
||||
}
|
||||
} // namespace ucoro
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "ucoro/single_thread.h"
|
||||
#include "psco/single_thread.h"
|
||||
#include <stdexcept>
|
||||
namespace ucoro {
|
||||
namespace psco {
|
||||
Single_Thread_Scheduler::Single_Thread_Scheduler() = default;
|
||||
Single_Thread_Scheduler::~Single_Thread_Scheduler() = default;
|
||||
void Single_Thread_Scheduler::reset() {
|
||||
@@ -19,13 +19,8 @@ namespace ucoro {
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Abandoned tasks from a previous run may still be suspended on
|
||||
// async_wait(). Do not discard their waiters; release them so they
|
||||
// can observe cancellation and unwind.
|
||||
release_waiters_locked();
|
||||
}
|
||||
// garbage is destroyed outside mtx_, so coroutine frames are never destroyed
|
||||
// while the scheduler lock is held.
|
||||
}
|
||||
void Single_Thread_Scheduler::post(std::function<void()> fn) {
|
||||
{
|
||||
@@ -139,7 +134,6 @@ namespace ucoro {
|
||||
std::lock_guard<std::mutex> g(mtx_);
|
||||
cleanup_abandoned_tasks_locked(garbage);
|
||||
}
|
||||
// garbage is destroyed outside mtx_.
|
||||
}
|
||||
std::size_t Single_Thread_Scheduler::abandoned_task_count() {
|
||||
std::vector<Abandoned_Task> garbage;
|
||||
@@ -158,7 +152,7 @@ namespace ucoro {
|
||||
try {
|
||||
std::rethrow_exception(exception);
|
||||
}
|
||||
catch (const ucoro::operation_cancelled&) {
|
||||
catch (const psco::operation_cancelled&) {
|
||||
return true;
|
||||
}
|
||||
catch (...) {
|
||||
@@ -182,4 +176,4 @@ namespace ucoro {
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace ucoro
|
||||
}
|
||||
+13
-13
@@ -1,4 +1,4 @@
|
||||
#include "ucoro/single_thread.h"
|
||||
#include "psco/single_thread.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
@@ -13,10 +13,10 @@
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
namespace {
|
||||
using Scheduler = ucoro::Single_Thread_Scheduler;
|
||||
using Scheduler = psco::Single_Thread_Scheduler;
|
||||
std::exception_ptr make_operation_cancelled_exception() {
|
||||
try {
|
||||
throw ucoro::operation_cancelled{};
|
||||
throw psco::operation_cancelled{};
|
||||
}
|
||||
catch (...) {
|
||||
return std::current_exception();
|
||||
@@ -30,12 +30,12 @@ namespace {
|
||||
return std::current_exception();
|
||||
}
|
||||
}
|
||||
ucoro::awaitable<void> scheduler_wait_task(
|
||||
psco::awaitable<void> scheduler_wait_task(
|
||||
Scheduler& scheduler,
|
||||
std::atomic<int>& stage
|
||||
) {
|
||||
stage.store(1, std::memory_order_release);
|
||||
co_await ucoro::callback_awaitable<void>([&scheduler](auto done) mutable {
|
||||
co_await psco::callback_awaitable<void>([&scheduler](auto done) mutable {
|
||||
scheduler.async_wait([done = std::move(done)]() mutable {
|
||||
done();
|
||||
});
|
||||
@@ -43,7 +43,7 @@ namespace {
|
||||
stage.store(2, std::memory_order_release);
|
||||
co_return;
|
||||
}
|
||||
ucoro::awaitable<void> scheduler_wait_then_return_task(
|
||||
psco::awaitable<void> scheduler_wait_then_return_task(
|
||||
Scheduler& scheduler
|
||||
) {
|
||||
std::atomic<int> ignored{0};
|
||||
@@ -70,7 +70,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
};
|
||||
ucoro::awaitable<void> task_with_destructor_that_posts(
|
||||
psco::awaitable<void> task_with_destructor_that_posts(
|
||||
Scheduler& scheduler,
|
||||
std::atomic<int>& stage,
|
||||
std::atomic<int>& destructor_posted_callbacks
|
||||
@@ -81,7 +81,7 @@ namespace {
|
||||
}
|
||||
void expect_operation_cancelled(std::exception_ptr exception) {
|
||||
ASSERT_TRUE(exception != nullptr);
|
||||
EXPECT_THROW(std::rethrow_exception(exception), ucoro::operation_cancelled);
|
||||
EXPECT_THROW(std::rethrow_exception(exception), psco::operation_cancelled);
|
||||
}
|
||||
}
|
||||
TEST(SingleThreadSchedulerTest, CompileTimeProperties) {
|
||||
@@ -345,8 +345,8 @@ TEST(SingleThreadSchedulerTest, AbandonRemainingTasksKeepsTaskUntilReleasedByWai
|
||||
std::condition_variable cv;
|
||||
bool completed = false;
|
||||
std::exception_ptr exception;
|
||||
std::map<int, ucoro::awaitable<void>> tasks;
|
||||
auto task = scheduler_wait_task(scheduler, stage).detach_with_callback(
|
||||
std::map<int, psco::awaitable<void>> tasks;
|
||||
auto task = psco::with_callback(scheduler_wait_task(scheduler, stage),
|
||||
[&](std::exception_ptr result) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
@@ -380,8 +380,8 @@ TEST(SingleThreadSchedulerTest, ResetDoesNotDiscardAbandonedCallbacks) {
|
||||
std::condition_variable cv;
|
||||
bool completed = false;
|
||||
std::exception_ptr exception;
|
||||
std::map<int, ucoro::awaitable<void>> tasks;
|
||||
auto task = scheduler_wait_task(scheduler, stage).detach_with_callback(
|
||||
std::map<int, psco::awaitable<void>> tasks;
|
||||
auto task = psco::with_callback(scheduler_wait_task(scheduler, stage),
|
||||
[&](std::exception_ptr result) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
@@ -410,7 +410,7 @@ TEST(SingleThreadSchedulerTest, CleanupAbandonedTasksDoesNotDestroyCoroutineFram
|
||||
Scheduler scheduler;
|
||||
std::atomic<int> stage{0};
|
||||
std::atomic<int> destructor_posted_callbacks{0};
|
||||
std::map<int, ucoro::awaitable<void>> tasks;
|
||||
std::map<int, psco::awaitable<void>> tasks;
|
||||
auto task = task_with_destructor_that_posts(
|
||||
scheduler,
|
||||
stage,
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "ucoro/awaitable.hpp"
|
||||
#include "psco/awaitable.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
@@ -88,58 +88,51 @@ namespace {
|
||||
NonDefaultValue& operator=(NonDefaultValue&&) noexcept = default;
|
||||
int value;
|
||||
};
|
||||
ucoro::awaitable<int> compute_callback_sync(int value) {
|
||||
auto ret = co_await ucoro::callback_awaitable<int>([value](auto handler) {
|
||||
psco::awaitable<int> compute_callback_sync(int value) {
|
||||
auto ret = co_await psco::callback_awaitable<int>([value](auto handler) {
|
||||
handler(value * 100);
|
||||
});
|
||||
co_return value + ret;
|
||||
}
|
||||
ucoro::awaitable<int> compute_callback_async(Simulated_Async_Callbacks& async, int value) {
|
||||
auto ret = co_await ucoro::callback_awaitable<int>([&async, value](auto handler) {
|
||||
psco::awaitable<int> compute_callback_async(Simulated_Async_Callbacks& async, int value) {
|
||||
auto ret = co_await psco::callback_awaitable<int>([&async, value](auto handler) {
|
||||
async.async_int(value, std::move(handler));
|
||||
});
|
||||
co_return value + ret;
|
||||
}
|
||||
ucoro::awaitable<void> compute_callback_async_void(Simulated_Async_Callbacks& async, std::atomic<int>& flag) {
|
||||
co_await ucoro::callback_awaitable<void>([&async, &flag](auto handler) {
|
||||
psco::awaitable<void> compute_callback_async_void(Simulated_Async_Callbacks& async, std::atomic<int>& flag) {
|
||||
co_await psco::callback_awaitable<void>([&async, &flag](auto handler) {
|
||||
async.async_void([&flag, handler = std::move(handler)]() mutable {
|
||||
flag.store(1, std::memory_order_release);
|
||||
handler();
|
||||
});
|
||||
});
|
||||
co_return;
|
||||
}
|
||||
ucoro::awaitable<int> compute_non_default_value(Simulated_Async_Callbacks& async) {
|
||||
auto value = co_await ucoro::callback_awaitable<NonDefaultValue>([&async](auto handler) {
|
||||
psco::awaitable<int> compute_non_default_value(Simulated_Async_Callbacks& async) {
|
||||
auto value = co_await psco::callback_awaitable<NonDefaultValue>([&async](auto handler) {
|
||||
async.async_value(NonDefaultValue{42}, std::move(handler));
|
||||
});
|
||||
co_return value.value;
|
||||
}
|
||||
ucoro::awaitable<std::string> read_local_string() {
|
||||
co_return co_await ucoro::local_storage_t<std::string>{};
|
||||
}
|
||||
ucoro::awaitable<std::pair<std::string, std::string>> read_parent_and_detached_local() {
|
||||
auto inherited = co_await read_local_string();
|
||||
auto detached = co_await read_local_string().detach(std::string{"detached-local"});
|
||||
co_return std::pair<std::string, std::string>{std::move(inherited), std::move(detached)};
|
||||
}
|
||||
ucoro::awaitable<int> throw_int_task() {
|
||||
psco::awaitable<int> throw_int_task() {
|
||||
throw std::runtime_error("int-task-error");
|
||||
co_return 1;
|
||||
}
|
||||
ucoro::awaitable<void> throw_void_task() {
|
||||
psco::awaitable<void> throw_void_task() {
|
||||
throw std::runtime_error("void-task-error");
|
||||
co_return;
|
||||
}
|
||||
ucoro::awaitable<std::unique_ptr<int>> make_unique_value() {
|
||||
psco::awaitable<std::unique_ptr<int>> make_unique_value() {
|
||||
co_return std::make_unique<int>(77);
|
||||
}
|
||||
ucoro::awaitable<void> recursive_task(int value) {
|
||||
psco::awaitable<void> recursive_task(int value) {
|
||||
if (value == 0) {
|
||||
co_return;
|
||||
}
|
||||
co_await recursive_task(value - 1);
|
||||
}
|
||||
ucoro::awaitable<void> mark_on_run(std::atomic<int>& flag) {
|
||||
psco::awaitable<void> mark_on_run(std::atomic<int>& flag) {
|
||||
flag.fetch_add(1, std::memory_order_acq_rel);
|
||||
co_return;
|
||||
}
|
||||
@@ -157,36 +150,29 @@ namespace {
|
||||
live_count().fetch_sub(1, std::memory_order_acq_rel);
|
||||
}
|
||||
};
|
||||
ucoro::awaitable<void> sync_probe_task() {
|
||||
psco::awaitable<void> sync_probe_task() {
|
||||
AllocationProbe probe;
|
||||
co_return;
|
||||
}
|
||||
ucoro::awaitable<void> async_probe_task(Simulated_Async_Callbacks& async) {
|
||||
psco::awaitable<void> async_probe_task(Simulated_Async_Callbacks& async) {
|
||||
AllocationProbe probe;
|
||||
co_await ucoro::callback_awaitable<void>([&async](auto handler) {
|
||||
co_await psco::callback_awaitable<void>([&async](auto handler) {
|
||||
async.async_void(std::move(handler));
|
||||
});
|
||||
co_return;
|
||||
}
|
||||
ucoro::awaitable<void> manual_probe_task(Manual_Async_Callbacks& async, std::atomic<int>& after_await) {
|
||||
psco::awaitable<void> manual_probe_task(Manual_Async_Callbacks& async, std::atomic<int>& after_await) {
|
||||
AllocationProbe probe;
|
||||
auto value = co_await ucoro::callback_awaitable<int>([&async](auto handler) {
|
||||
auto value = co_await psco::callback_awaitable<int>([&async](auto handler) {
|
||||
async.async_int(std::move(handler));
|
||||
});
|
||||
after_await.store(value, std::memory_order_release);
|
||||
co_return;
|
||||
}
|
||||
void compile_time_checks() {
|
||||
static_assert(ucoro::concepts::local_storage_type<ucoro::local_storage_t<void>>,
|
||||
"local_storage_t check failed");
|
||||
using local_storage_template_parameter = ucoro::traits::template_parameter_of<
|
||||
decltype(ucoro::local_storage), ucoro::local_storage_t>;
|
||||
static_assert(std::is_void_v<local_storage_template_parameter>,
|
||||
"local_storage should be local_storage_t<void>");
|
||||
// Keep this test limited to stable library traits. MSVC 2019 has fragile parsing for
|
||||
// static_assert checks involving coroutine awaiter SFINAE and generic lambdas. Runtime
|
||||
// tests below cover CallbackAwaiter and awaitable behavior directly.
|
||||
static_assert(ucoro::concepts::awaitable_type<ucoro::awaitable<int>>,
|
||||
static_assert(psco::concepts::awaitable_type<psco::awaitable<int>>,
|
||||
"awaitable<int> should be ucoro awaitable");
|
||||
static_assert(!ucoro::concepts::awaitable_type<int>, "int should not be ucoro awaitable");
|
||||
static_assert(!psco::concepts::awaitable_type<int>, "int should not be ucoro awaitable");
|
||||
}
|
||||
}
|
||||
TEST(UcoroTest, CompileTimeTraitsMatchCoreTypes) {
|
||||
@@ -194,30 +180,26 @@ TEST(UcoroTest, CompileTimeTraitsMatchCoreTypes) {
|
||||
SUCCEED();
|
||||
}
|
||||
TEST(UcoroTest, CallbackAwaitableCanCompleteSynchronously) {
|
||||
EXPECT_EQ(ucoro::sync_await(compute_callback_sync(2)), 202);
|
||||
EXPECT_EQ(psco::sync_await(compute_callback_sync(2)), 202);
|
||||
}
|
||||
TEST(UcoroTest, SyncAwaitWaitsForSimulatedAsyncThreadCallback) {
|
||||
Simulated_Async_Callbacks async;
|
||||
EXPECT_EQ(ucoro::sync_await(compute_callback_async(async, 3)), 303);
|
||||
EXPECT_EQ(psco::sync_await(compute_callback_async(async, 3)), 303);
|
||||
}
|
||||
TEST(UcoroTest, CallbackAwaitableSupportsVoidCompletion) {
|
||||
Simulated_Async_Callbacks async;
|
||||
std::atomic<int> flag{0};
|
||||
ucoro::sync_await(compute_callback_async_void(async, flag));
|
||||
psco::sync_await(compute_callback_async_void(async, flag));
|
||||
EXPECT_EQ(flag.load(std::memory_order_acquire), 1);
|
||||
}
|
||||
TEST(UcoroTest, CallbackAwaiterSupportsNonDefaultConstructibleValue) {
|
||||
Simulated_Async_Callbacks async;
|
||||
EXPECT_EQ(ucoro::sync_await(compute_non_default_value(async)), 42);
|
||||
}
|
||||
TEST(UcoroTest, DetachLocalOverridesParentLocalWhenAwaited) {
|
||||
auto values = ucoro::sync_await(read_parent_and_detached_local(), std::string{"parent-local"});
|
||||
EXPECT_EQ(values.first, "parent-local");
|
||||
EXPECT_EQ(values.second, "detached-local");
|
||||
EXPECT_EQ(psco::sync_await(compute_non_default_value(async)), 42);
|
||||
}
|
||||
|
||||
TEST(UcoroTest, LateDuplicateCallbackAfterAwaiterDestructionIsIgnored) {
|
||||
std::thread late_callback;
|
||||
auto value = ucoro::sync_await(ucoro::callback_awaitable<int>([&late_callback](auto handler) mutable {
|
||||
auto value = psco::sync_await(psco::callback_awaitable<int>([&late_callback](auto handler) mutable {
|
||||
handler(11);
|
||||
late_callback = std::thread([handler = std::move(handler)]() mutable {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
@@ -230,18 +212,18 @@ TEST(UcoroTest, LateDuplicateCallbackAfterAwaiterDestructionIsIgnored) {
|
||||
}
|
||||
}
|
||||
TEST(UcoroTest, SyncAwaitRethrowsIntTaskException) {
|
||||
EXPECT_THROW(static_cast<void>(ucoro::sync_await(throw_int_task())), std::runtime_error);
|
||||
EXPECT_THROW(static_cast<void>(psco::sync_await(throw_int_task())), std::runtime_error);
|
||||
}
|
||||
TEST(UcoroTest, SyncAwaitRethrowsVoidTaskException) {
|
||||
EXPECT_THROW(ucoro::sync_await(throw_void_task()), std::runtime_error);
|
||||
EXPECT_THROW(psco::sync_await(throw_void_task()), std::runtime_error);
|
||||
}
|
||||
TEST(UcoroTest, AwaitableReturnsMoveOnlyValue) {
|
||||
auto value = ucoro::sync_await(make_unique_value());
|
||||
auto value = psco::sync_await(make_unique_value());
|
||||
ASSERT_NE(value, nullptr);
|
||||
EXPECT_EQ(*value, 77);
|
||||
}
|
||||
TEST(UcoroTest, DeepRecursiveAwaitChainCompletes) {
|
||||
ucoro::sync_await(recursive_task(10000));
|
||||
psco::sync_await(recursive_task(10000));
|
||||
SUCCEED();
|
||||
}
|
||||
TEST(UcoroTest, LazyAwaitableDestructorDoesNotStartCoroutine) {
|
||||
@@ -262,7 +244,7 @@ TEST(UcoroTest, ExplicitStartRunsOwnedCoroutine) {
|
||||
TEST(UcoroTest, StartDetachedRunsAsyncCoroutine) {
|
||||
Simulated_Async_Callbacks async;
|
||||
std::atomic<int> flag{0};
|
||||
ucoro::start_detached(compute_callback_async_void(async, flag));
|
||||
compute_callback_async_void(async, flag).start_detached();
|
||||
async.join_all();
|
||||
EXPECT_EQ(flag.load(std::memory_order_acquire), 1);
|
||||
}
|
||||
@@ -276,7 +258,7 @@ TEST(UcoroTest, ExplicitStartDestroysSynchronouslyCompletedCoroutine) {
|
||||
TEST(UcoroTest, StartDetachedKeepsAsyncCoroutineAliveUntilCompletionThenDestroysIt) {
|
||||
Simulated_Async_Callbacks async;
|
||||
AllocationProbe::live_count().store(0, std::memory_order_release);
|
||||
ucoro::start_detached(async_probe_task(async));
|
||||
async_probe_task(async).start_detached();
|
||||
EXPECT_EQ(AllocationProbe::live_count().load(std::memory_order_acquire), 1);
|
||||
async.join_all();
|
||||
EXPECT_EQ(AllocationProbe::live_count().load(std::memory_order_acquire), 0);
|
||||
@@ -285,7 +267,8 @@ TEST(UcoroTest, ResetStartedPendingTaskCancelsWithoutDestroyingFrameUntilCallbac
|
||||
Manual_Async_Callbacks async;
|
||||
std::atomic<int> after_await{0};
|
||||
AllocationProbe::live_count().store(0, std::memory_order_release);
|
||||
auto task = ucoro::coro_start(manual_probe_task(async, after_await));
|
||||
auto task = manual_probe_task(async, after_await);
|
||||
task.start();
|
||||
ASSERT_TRUE(async.has_int_handler());
|
||||
EXPECT_TRUE(task.valid());
|
||||
EXPECT_EQ(AllocationProbe::live_count().load(std::memory_order_acquire), 1);
|
||||
@@ -304,8 +287,7 @@ TEST(UcoroTest, ResetStartedPendingTaskReportsOperationCancelledToCompletionHand
|
||||
std::condition_variable cv;
|
||||
bool completed = false;
|
||||
std::exception_ptr exception;
|
||||
auto task = ucoro::coro_start(manual_probe_task(async, after_await), std::any{},
|
||||
[&](std::exception_ptr result) {
|
||||
auto task = psco::with_callback(manual_probe_task(async, after_await), [&](std::exception_ptr result) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
exception = result;
|
||||
@@ -313,6 +295,7 @@ TEST(UcoroTest, ResetStartedPendingTaskReportsOperationCancelledToCompletionHand
|
||||
}
|
||||
cv.notify_one();
|
||||
});
|
||||
task.start();
|
||||
ASSERT_TRUE(async.has_int_handler());
|
||||
task.reset();
|
||||
async.complete_int(456);
|
||||
@@ -322,7 +305,7 @@ TEST(UcoroTest, ResetStartedPendingTaskReportsOperationCancelledToCompletionHand
|
||||
}
|
||||
EXPECT_EQ(after_await.load(std::memory_order_acquire), 0);
|
||||
ASSERT_TRUE(exception != nullptr);
|
||||
EXPECT_THROW(std::rethrow_exception(exception), ucoro::operation_cancelled);
|
||||
EXPECT_THROW(std::rethrow_exception(exception), psco::operation_cancelled);
|
||||
}
|
||||
TEST(UcoroTest, ConcurrentResetAndCallbackCompletionDoesNotCrash) {
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
@@ -331,14 +314,14 @@ TEST(UcoroTest, ConcurrentResetAndCallbackCompletionDoesNotCrash) {
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
bool completed = false;
|
||||
auto task = ucoro::coro_start(manual_probe_task(async, after_await), std::any{},
|
||||
[&](std::exception_ptr) {
|
||||
auto task = psco::with_callback(manual_probe_task(async, after_await), [&](std::exception_ptr) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
completed = true;
|
||||
}
|
||||
cv.notify_one();
|
||||
});
|
||||
task.start();
|
||||
ASSERT_TRUE(async.has_int_handler());
|
||||
std::thread reset_thread([&task] {
|
||||
task.reset();
|
||||
@@ -362,8 +345,7 @@ TEST(UcoroTest, CancelStartedPendingTaskReportsOperationCancelledWithoutImmediat
|
||||
std::condition_variable cv;
|
||||
bool completed = false;
|
||||
std::exception_ptr exception;
|
||||
auto task = ucoro::coro_start(manual_probe_task(async, after_await), std::any{},
|
||||
[&](std::exception_ptr result) {
|
||||
auto task = psco::with_callback(manual_probe_task(async, after_await), [&](std::exception_ptr result) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
exception = result;
|
||||
@@ -371,6 +353,7 @@ TEST(UcoroTest, CancelStartedPendingTaskReportsOperationCancelledWithoutImmediat
|
||||
}
|
||||
cv.notify_one();
|
||||
});
|
||||
task.start();
|
||||
ASSERT_TRUE(async.has_int_handler());
|
||||
EXPECT_TRUE(task.valid());
|
||||
task.cancel();
|
||||
@@ -382,14 +365,15 @@ TEST(UcoroTest, CancelStartedPendingTaskReportsOperationCancelledWithoutImmediat
|
||||
EXPECT_FALSE(task.valid());
|
||||
EXPECT_EQ(after_await.load(std::memory_order_acquire), 0);
|
||||
ASSERT_TRUE(exception != nullptr);
|
||||
EXPECT_THROW(std::rethrow_exception(exception), ucoro::operation_cancelled);
|
||||
EXPECT_THROW(std::rethrow_exception(exception), psco::operation_cancelled);
|
||||
}
|
||||
TEST(UcoroTest, OwnedPendingTaskDestructorAbandonsAndDestroysAfterCallback) {
|
||||
Manual_Async_Callbacks async;
|
||||
std::atomic<int> after_await{0};
|
||||
AllocationProbe::live_count().store(0, std::memory_order_release);
|
||||
{
|
||||
auto task = ucoro::coro_start(manual_probe_task(async, after_await));
|
||||
auto task = manual_probe_task(async, after_await);
|
||||
task.start();
|
||||
ASSERT_TRUE(async.has_int_handler());
|
||||
EXPECT_TRUE(task.valid());
|
||||
EXPECT_EQ(AllocationProbe::live_count().load(std::memory_order_acquire), 1);
|
||||
@@ -400,25 +384,24 @@ TEST(UcoroTest, OwnedPendingTaskDestructorAbandonsAndDestroysAfterCallback) {
|
||||
EXPECT_EQ(after_await.load(std::memory_order_acquire), 0);
|
||||
EXPECT_EQ(AllocationProbe::live_count().load(std::memory_order_acquire), 0);
|
||||
}
|
||||
TEST(UcoroTest, MissingLocalStorageThrowsLogicError) {
|
||||
EXPECT_THROW(static_cast<void>(ucoro::sync_await(read_local_string())), std::logic_error);
|
||||
}
|
||||
|
||||
TEST(UcoroTest, CompletionHandlerExceptionIsNotReportedByCallingHandlerTwice) {
|
||||
std::atomic<int> calls{0};
|
||||
auto task = ucoro::coro_start(sync_probe_task(), std::any{},
|
||||
[&](std::exception_ptr) {
|
||||
auto task = psco::with_callback(sync_probe_task(), [&](std::exception_ptr) {
|
||||
calls.fetch_add(1, std::memory_order_acq_rel);
|
||||
throw std::runtime_error("handler-error");
|
||||
});
|
||||
task.start();
|
||||
EXPECT_FALSE(task.valid());
|
||||
EXPECT_EQ(calls.load(std::memory_order_acquire), 1);
|
||||
}
|
||||
namespace {
|
||||
ucoro::awaitable<void> callback_that_must_not_start_after_abandon(std::atomic<int>& callback_started) {
|
||||
co_await ucoro::callback_awaitable<void>([&callback_started](auto handler) {
|
||||
psco::awaitable<void> callback_that_must_not_start_after_abandon(std::atomic<int>& callback_started) {
|
||||
co_await psco::callback_awaitable<void>([&callback_started](auto handler) {
|
||||
callback_started.fetch_add(1, std::memory_order_acq_rel);
|
||||
handler();
|
||||
});
|
||||
co_return;
|
||||
}
|
||||
}
|
||||
TEST(UcoroTest, AbandonedTaskDoesNotStartNewCallbackAwaiter) {
|
||||
@@ -436,21 +419,21 @@ namespace {
|
||||
constexpr bool await_suspend(std::coroutine_handle<>) const noexcept { return false; }
|
||||
constexpr int await_resume() const noexcept { return value; }
|
||||
};
|
||||
ucoro::awaitable<int> await_plain_third_party_awaiter() {
|
||||
psco::awaitable<int> await_plain_third_party_awaiter() {
|
||||
auto value = co_await Immediate_Third_Party_Awaiter{41};
|
||||
co_return value + 1;
|
||||
}
|
||||
struct External_Async_Operation {
|
||||
Manual_Async_Callbacks* async;
|
||||
};
|
||||
ucoro::awaitable<int> external_operation_as_ucoro(External_Async_Operation op) {
|
||||
auto value = co_await ucoro::callback_awaitable<int>([op](auto handler) mutable {
|
||||
psco::awaitable<int> external_operation_as_ucoro(External_Async_Operation op) {
|
||||
auto value = co_await psco::callback_awaitable<int>([op](auto handler) mutable {
|
||||
op.async->async_int(std::move(handler));
|
||||
});
|
||||
co_return value;
|
||||
}
|
||||
}
|
||||
namespace ucoro {
|
||||
namespace psco {
|
||||
template <>
|
||||
struct await_transformer<External_Async_Operation> {
|
||||
static auto await_transform(External_Async_Operation op) {
|
||||
@@ -459,52 +442,53 @@ namespace ucoro {
|
||||
};
|
||||
}
|
||||
namespace {
|
||||
ucoro::awaitable<int> await_external_operation(Manual_Async_Callbacks& async) {
|
||||
psco::awaitable<int> await_external_operation(Manual_Async_Callbacks& async) {
|
||||
auto value = co_await External_Async_Operation{&async};
|
||||
co_return value + 1;
|
||||
}
|
||||
ucoro::awaitable<int> callback_registration_throws() {
|
||||
auto value = co_await ucoro::callback_awaitable<int>([](auto) {
|
||||
psco::awaitable<int> callback_registration_throws() {
|
||||
auto value = co_await psco::callback_awaitable<int>([](auto) {
|
||||
throw std::runtime_error("registration-error");
|
||||
});
|
||||
co_return value;
|
||||
}
|
||||
ucoro::awaitable<int> sequential_callbacks(Simulated_Async_Callbacks& async) {
|
||||
auto first = co_await ucoro::callback_awaitable<int>([&async](auto handler) {
|
||||
psco::awaitable<int> sequential_callbacks(Simulated_Async_Callbacks& async) {
|
||||
auto first = co_await psco::callback_awaitable<int>([&async](auto handler) {
|
||||
async.async_int(1, std::move(handler));
|
||||
});
|
||||
auto second = co_await ucoro::callback_awaitable<int>([&async](auto handler) {
|
||||
auto second = co_await psco::callback_awaitable<int>([&async](auto handler) {
|
||||
async.async_int(2, std::move(handler));
|
||||
});
|
||||
co_return first + second;
|
||||
}
|
||||
ucoro::awaitable<void> pending_child_callback(
|
||||
psco::awaitable<void> pending_child_callback(
|
||||
Manual_Async_Callbacks& async,
|
||||
std::atomic<int>& child_after_await) {
|
||||
auto value = co_await ucoro::callback_awaitable<int>([&async](auto handler) {
|
||||
auto value = co_await psco::callback_awaitable<int>([&async](auto handler) {
|
||||
async.async_int(std::move(handler));
|
||||
});
|
||||
child_after_await.store(value, std::memory_order_release);
|
||||
co_return;
|
||||
}
|
||||
ucoro::awaitable<void> parent_waiting_on_pending_child(
|
||||
psco::awaitable<void> parent_waiting_on_pending_child(
|
||||
Manual_Async_Callbacks& async,
|
||||
std::atomic<int>& parent_after_child,
|
||||
std::atomic<int>& child_after_await) {
|
||||
co_await pending_child_callback(async, child_after_await);
|
||||
parent_after_child.store(1, std::memory_order_release);
|
||||
co_return;
|
||||
}
|
||||
}
|
||||
TEST(UcoroTest, AllowsPlainThirdPartyAwaiterThroughAwaitTransform) {
|
||||
EXPECT_EQ(ucoro::sync_await(await_plain_third_party_awaiter()), 42);
|
||||
EXPECT_EQ(psco::sync_await(await_plain_third_party_awaiter()), 42);
|
||||
}
|
||||
TEST(UcoroTest, AwaitTransformerAdaptsExternalOperation) {
|
||||
Manual_Async_Callbacks async;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
bool completed = false;
|
||||
ucoro::traits::exception_with_result_t<int> result;
|
||||
auto task = ucoro::coro_start(await_external_operation(async), std::any{},
|
||||
[&](ucoro::traits::exception_with_result_t<int> r) mutable {
|
||||
psco::traits::exception_with_result_t<int> result;
|
||||
auto task = psco::with_callback(await_external_operation(async), [&](psco::traits::exception_with_result_t<int> r) mutable {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
result = std::move(r);
|
||||
@@ -512,6 +496,7 @@ TEST(UcoroTest, AwaitTransformerAdaptsExternalOperation) {
|
||||
}
|
||||
cv.notify_one();
|
||||
});
|
||||
task.start();
|
||||
ASSERT_TRUE(async.has_int_handler());
|
||||
async.complete_int(41);
|
||||
{
|
||||
@@ -523,11 +508,11 @@ TEST(UcoroTest, AwaitTransformerAdaptsExternalOperation) {
|
||||
EXPECT_EQ(std::get<int>(result), 42);
|
||||
}
|
||||
TEST(UcoroTest, CallbackRegistrationExceptionPropagatesThroughSyncAwait) {
|
||||
EXPECT_THROW(static_cast<void>(ucoro::sync_await(callback_registration_throws())), std::runtime_error);
|
||||
EXPECT_THROW(static_cast<void>(psco::sync_await(callback_registration_throws())), std::runtime_error);
|
||||
}
|
||||
TEST(UcoroTest, SequentialCallbackAwaitersUseIndependentState) {
|
||||
Simulated_Async_Callbacks async;
|
||||
EXPECT_EQ(ucoro::sync_await(sequential_callbacks(async)), 300);
|
||||
EXPECT_EQ(psco::sync_await(sequential_callbacks(async)), 300);
|
||||
}
|
||||
TEST(UcoroTest, ResetParentPendingOnChildAbandonsChildAndSkipsContinuations) {
|
||||
Manual_Async_Callbacks async;
|
||||
@@ -537,9 +522,8 @@ TEST(UcoroTest, ResetParentPendingOnChildAbandonsChildAndSkipsContinuations) {
|
||||
std::condition_variable cv;
|
||||
bool completed = false;
|
||||
std::exception_ptr exception;
|
||||
auto task = ucoro::coro_start(
|
||||
auto task = psco::with_callback(
|
||||
parent_waiting_on_pending_child(async, parent_after_child, child_after_await),
|
||||
std::any{},
|
||||
[&](std::exception_ptr result) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
@@ -548,6 +532,7 @@ TEST(UcoroTest, ResetParentPendingOnChildAbandonsChildAndSkipsContinuations) {
|
||||
}
|
||||
cv.notify_one();
|
||||
});
|
||||
task.start();
|
||||
ASSERT_TRUE(async.has_int_handler());
|
||||
task.reset();
|
||||
async.complete_int(99);
|
||||
@@ -558,5 +543,5 @@ TEST(UcoroTest, ResetParentPendingOnChildAbandonsChildAndSkipsContinuations) {
|
||||
EXPECT_EQ(parent_after_child.load(std::memory_order_acquire), 0);
|
||||
EXPECT_EQ(child_after_await.load(std::memory_order_acquire), 0);
|
||||
ASSERT_TRUE(exception != nullptr);
|
||||
EXPECT_THROW(std::rethrow_exception(exception), ucoro::operation_cancelled);
|
||||
EXPECT_THROW(std::rethrow_exception(exception), psco::operation_cancelled);
|
||||
}
|
||||
Reference in New Issue
Block a user