Skip to content

Commit

Permalink
Merge divergent heads
Browse files Browse the repository at this point in the history
  • Loading branch information
dermesser committed Jan 2, 2025
2 parents 2658803 + 0d6304a commit 1627f4e
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 34 deletions.
20 changes: 10 additions & 10 deletions test/promise_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ TEST(PromiseTest, awaitTwice) {
run_loop(setup);
}

TEST(PromiseTest, awaitTwiceImmediateReturn) {
auto setup = [](const Loop &loop) -> uvco::Promise<void> {
Promise<int> promise = []() -> uvco::Promise<int> { co_return 1; }();
EXPECT_EQ(co_await promise, 1);
EXPECT_THROW({ co_await promise; }, UvcoException);
};

run_loop(setup);
}

TEST(PromiseTest, stackVariableAccessedSafely) {
// Test fails through asan if stackVar is accessed in a use-after-return
// manner.
Expand All @@ -57,16 +67,6 @@ TEST(PromiseTest, stackVariableAccessedSafely) {
run_loop(setup);
}

TEST(PromiseTest, awaitTwiceImmediateReturn) {
auto setup = [](const Loop &loop) -> uvco::Promise<void> {
Promise<int> promise = []() -> uvco::Promise<int> { co_return 1; }();
EXPECT_EQ(co_await promise, 1);
EXPECT_THROW({ co_await promise; }, UvcoException);
};

run_loop(setup);
}

TEST(PromiseTest, yield) {
bool awaited = false;
auto setup = [&](const Loop &loop) -> uvco::Promise<void> {
Expand Down
34 changes: 28 additions & 6 deletions test/select_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,20 @@ TEST(SelectTest, selectReturnsSimultaneously) {

auto selectSet = SelectSet{promiseObject1, promiseObject2};
auto selected = co_await selectSet;
EXPECT_EQ(selected.size(), 2);
EXPECT_EQ(co_await std::get<0>(selected[0]).get(), 1);
EXPECT_EQ(co_await std::get<1>(selected[1]).get(), 2);
EXPECT_EQ(selected.size(), 1);
BOOST_ASSERT(selected.size() == 1);
switch (selected[0].index()) {
case 0:
EXPECT_EQ(co_await std::get<0>(selected[0]).get(), 1);
EXPECT_EQ(co_await promiseObject2, 2);
break;
case 1:
EXPECT_EQ(co_await std::get<1>(selected[0]).get(), 2);
EXPECT_EQ(co_await promiseObject1, 1);
break;
default:
EXPECT_FALSE(true);
}
co_return;
};

Expand Down Expand Up @@ -115,9 +126,20 @@ TEST(SelectTest, selectSetMany) {
promiseObject1, promiseObject2, promiseObject3, promiseObject4,
promiseObject1a, promiseObject2a, promiseObject3a, promiseObject4a};
const auto selected = co_await selectSet;
EXPECT_EQ(selected.size(), 2);
EXPECT_EQ(co_await std::get<0>(selected[0]).get(), 1);
EXPECT_EQ(co_await std::get<4>(selected[1]).get(), 1);
EXPECT_EQ(selected.size(), 1);
BOOST_ASSERT(selected.size() == 1);
switch (selected[0].index()) {
case 0:
EXPECT_EQ(co_await std::get<0>(selected[0]).get(), 1);
EXPECT_EQ(co_await promiseObject1a, 1);
break;
case 4:
EXPECT_EQ(co_await std::get<4>(selected[0]).get(), 1);
EXPECT_EQ(co_await promiseObject1, 1);
break;
default:
EXPECT_FALSE(true);
}
co_return;
};

Expand Down
36 changes: 36 additions & 0 deletions uvco/promise/multipromise.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,42 @@ template <typename T> class MultiPromiseCore : public PromiseCore<T> {
}
}

void resume() {
if (PromiseCore<T>::handle_) {
BOOST_ASSERT(PromiseCore<T>::state_ == PromiseState::waitedOn);
PromiseCore<T>::state_ = PromiseState::resuming;
const auto handle = PromiseCore<T>::handle_.value();
PromiseCore<T>::handle_.reset();
handle.resume();
} else {
// This occurs if no co_await has occured until resume. Either the
// promise was not co_awaited, or the producing coroutine immediately
// returned a value. (await_ready() == true)
}

switch (PromiseCore<T>::state_) {
case PromiseState::init:
// Coroutine returns but nobody has awaited yet. This is fine.
PromiseCore<T>::state_ = PromiseState::finished;
break;
case PromiseState::resuming:
// Not entirely correct, but the resumed awaiting coroutine is not coming
// back to us.
PromiseCore<T>::state_ = PromiseState::finished;
break;
case PromiseState::waitedOn:
// state is waitedOn, but no handle is set - that's an error.
// BOOST_ASSERT_MSG(
// false,
// "PromiseCore::resume() called without handle in state waitedOn");
break;
case PromiseState::finished:
// Happens in MultiPromiseCore on co_return if the co_awaiter has lost
// interest. Harmless if !handle_ (asserted above).
break;
}
}

/// Mark generator as finished, yielding no more values. Called from within
/// the MultiPromise upon return_value and unhandled_exception. From hereon
/// awaiting the generator will either rethrow the thrown exception, or yield
Expand Down
14 changes: 13 additions & 1 deletion uvco/promise/promise.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "uvco/exception.h"
#include "uvco/internal/internal_utils.h"
#include "uvco/loop/loop.h"
#include "uvco/promise/promise_core.h"

#include <boost/assert.hpp>
Expand Down Expand Up @@ -103,6 +104,7 @@ template <typename T> class [[nodiscard]] Promise {
other.core_ = nullptr;
other.suspendedHandle_ = nullptr;
}

Promise &operator=(Promise<T> &&other) noexcept {
if (this == &other) {
return *this;
Expand All @@ -116,6 +118,7 @@ template <typename T> class [[nodiscard]] Promise {
other.suspendedHandle_ = nullptr;
return *this;
}

~Promise() {
if (core_ != nullptr) {
core_->delRef();
Expand All @@ -131,7 +134,7 @@ template <typename T> class [[nodiscard]] Promise {
/// Part of the coroutine protocol: called by `co_await p` where `p` is a
/// `Promise<T>`. The returned object is awaited on.
PromiseAwaiter_ operator co_await() {
schedule();
initialResume();
return PromiseAwaiter_{*core_};
}

Expand Down Expand Up @@ -165,6 +168,15 @@ template <typename T> class [[nodiscard]] Promise {
}

protected:
void initialResume() {
if (!suspendedHandle_) {
throw UvcoException("Promises can only be awaited once");
}
const auto suspended = suspendedHandle_;
suspendedHandle_ = nullptr;
suspended.resume();
}

/// Returned as awaiter object when `co_await`ing a promise.
///
/// Handles suspension of current coroutine and resumption upon fulfillment of
Expand Down
7 changes: 3 additions & 4 deletions uvco/promise/promise_core.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

#include "uvco/promise/promise_core.h"
#include "uvco/exception.h"
#include "uvco/loop/loop.h"

#include <fmt/core.h>
#include <uv.h>
Expand All @@ -15,7 +14,7 @@
namespace uvco {

void PromiseCore<void>::setHandle(std::coroutine_handle<> handle) {
if (state_ != PromiseState::init) {
if (state_ != PromiseState::init && state_ != PromiseState::resuming) {
throw UvcoException("PromiseCore is already awaited or has finished");
}
BOOST_ASSERT(state_ == PromiseState::init);
Expand All @@ -33,9 +32,9 @@ void PromiseCore<void>::resume() {
if (handle_) {
BOOST_ASSERT(state_ == PromiseState::waitedOn);
state_ = PromiseState::resuming;
auto resumeHandle = *handle_;
auto handle = *handle_;
handle_.reset();
Loop::enqueue(resumeHandle);
handle.resume();
} else {
// If a coroutine returned immediately, or nobody is co_awaiting the result.
}
Expand Down
16 changes: 7 additions & 9 deletions uvco/promise/promise_core.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// uvco (c) 2023 Lewin Bormann. See LICENSE for specific terms.
// uvco (c) 2023-2024 Lewin Bormann. See LICENSE for specific terms.

#pragma once

#include "uvco/exception.h"
#include "uvco/internal/internal_utils.h"
#include "uvco/loop/loop.h"

#include <boost/assert.hpp>
#include <fmt/core.h>
Expand All @@ -20,6 +19,7 @@
#include <variant>

namespace uvco {

/// @addtogroup Promise
/// @{

Expand Down Expand Up @@ -72,7 +72,7 @@ template <typename T> class PromiseCore : public RefCounted<PromiseCore<T>> {

/// Set the coroutine to be resumed once a result is ready.
virtual void setHandle(std::coroutine_handle<> handle) {
if (state_ != PromiseState::init) {
if (state_ != PromiseState::init && state_ != PromiseState::resuming) {
throw UvcoException("PromiseCore is already awaited or has finished");
}
handle_ = handle;
Expand All @@ -84,7 +84,8 @@ template <typename T> class PromiseCore : public RefCounted<PromiseCore<T>> {
void resetHandle() {
BOOST_ASSERT((state_ == PromiseState::waitedOn && handle_) ||
(state_ == PromiseState::finished && !handle_) ||
(state_ == PromiseState::init && !handle_));
(state_ == PromiseState::init && !handle_) ||
(state_ == PromiseState::resuming && !handle_));
if (state_ == PromiseState::waitedOn) {
handle_.reset();
state_ = PromiseState::init;
Expand Down Expand Up @@ -127,9 +128,9 @@ template <typename T> class PromiseCore : public RefCounted<PromiseCore<T>> {
if (handle_) {
BOOST_ASSERT(state_ == PromiseState::waitedOn);
state_ = PromiseState::resuming;
const auto resume = handle_.value();
const auto handle = handle_.value();
handle_.reset();
Loop::enqueue(resume);
handle.resume();
} else {
// This occurs if no co_await has occured until resume. Either the
// promise was not co_awaited, or the producing coroutine immediately
Expand All @@ -148,9 +149,6 @@ template <typename T> class PromiseCore : public RefCounted<PromiseCore<T>> {
break;
case PromiseState::waitedOn:
// state is waitedOn, but no handle is set - that's an error.
BOOST_ASSERT_MSG(
false,
"PromiseCore::resume() called without handle in state waitedOn");
break;
case PromiseState::finished:
// Happens in MultiPromiseCore on co_return if the co_awaiter has lost
Expand Down
6 changes: 2 additions & 4 deletions uvco/run.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ MultiPromise<unsigned> yield(unsigned count);
template <typename F, typename R>
concept MainFunction = std::is_invocable_r_v<Promise<R>, F, const Loop &>;

template <typename R, MainFunction<R> F>
R runMain(F main);
template <typename R, MainFunction<R> F> R runMain(F main);

/// Set up event loop, then run main function to set up promises.
/// Finally, clean up once the event loop has finished. An exception
Expand All @@ -46,8 +45,7 @@ R runMain(F main);
/// });
/// ```
///
template <typename R, MainFunction<R> F>
R runMain(F main) {
template <typename R, MainFunction<R> F> R runMain(F main) {
Loop loop;
Promise<R> promise = main(loop);
promise.schedule();
Expand Down

0 comments on commit 1627f4e

Please sign in to comment.