删减无关内容

This commit is contained in:
2026-06-24 14:57:07 +08:00
parent 373a86dd03
commit e921b5008e
10 changed files with 256 additions and 452 deletions
@@ -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
}
@@ -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);
}