Skip to content

Commit

Permalink
Introduce get_return_object_on_allocation_failure to Lazy (#338) (#340)
Browse files Browse the repository at this point in the history
This patch tries to introduce `get_return_object_on_allocation_failure()` to
the promise_type of Lazy.

Why do we want to introduce `get_return_object_on_allocation_failure()`?
Since
a coroutine will be roughly converted to:

```C++
void *frame_addr = ::operator new(required size);
__promise_ = new (frame_addr) __promise_type(...);
__return_object_ = __promise_.get_return_object();
co_await __promise_.initial_suspend();
try {
    function-body
} catch (...) {
    __promise_.unhandled_exception();
}
co_await __promise_.final_suspend();
```

Then we can find that the coroutine should be nounwind (noexcept)
naturally if
the constructor of the promise_type, the get_return_object() function,
the
initial_suspend, the unhandled_exception(), the final_suspend and the
allocation
function is noexcept.

For the specific coroutine type, Lazy, all the above except the
allocation function
is noexcept. So that we can make every Lazy function noexcept naturally
if we make
the allocation function nothrow. This is the reason why we want to
introduce
`get_return_object_on_allocation_failure()` to Lazy.

Note that the optimization may not work in some platforms due the ABI
limitations.
Since they need to consider the case that the destructor of an exception
can throw
exceptions.
  • Loading branch information
ChuanqiXu9 authored Jul 28, 2023
1 parent 39e9e23 commit fe847c3
Showing 1 changed file with 64 additions and 6 deletions.
70 changes: 64 additions & 6 deletions async_simple/coro/Lazy.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class LazyPromiseBase {
};

public:
LazyPromiseBase() : _executor(nullptr), _lazy_local(nullptr) {}
LazyPromiseBase() noexcept : _executor(nullptr), _lazy_local(nullptr) {}
// Lazily started, coroutine will not execute until first resume() is called
std::suspend_always initial_suspend() noexcept { return {}; }
FinalAwaiter final_suspend() noexcept { return {}; }
Expand Down Expand Up @@ -147,6 +147,8 @@ class LazyPromise : public LazyPromiseBase {

Lazy<T> get_return_object() noexcept;

static Lazy<T> get_return_object_on_allocation_failure() noexcept;

template <typename V>
void return_value(V&& value) noexcept(
std::is_nothrow_constructible_v<
Expand Down Expand Up @@ -190,7 +192,12 @@ class LazyPromise : public LazyPromiseBase {
template <>
class LazyPromise<void> : public LazyPromiseBase {
public:
LazyPromise() noexcept {}
~LazyPromise() noexcept {}

Lazy<void> get_return_object() noexcept;
static Lazy<void> get_return_object_on_allocation_failure() noexcept;

void return_void() noexcept {}
void unhandled_exception() noexcept {
_exception = std::current_exception();
Expand Down Expand Up @@ -336,8 +343,8 @@ class LazyBase {
_coro = nullptr;
}
};
explicit LazyBase(Handle coro) : _coro(coro) {}
LazyBase(LazyBase&& other) : _coro(std::move(other._coro)) {
explicit LazyBase(Handle coro) noexcept : _coro(coro) {}
LazyBase(LazyBase&& other) noexcept : _coro(std::move(other._coro)) {
other._coro = nullptr;
}

Expand All @@ -348,6 +355,10 @@ class LazyBase {

template <typename F>
void start(F&& callback) requires(std::is_invocable_v<F&&, Try<T>>) {
logicAssert(this->_coro.operator bool(),
"Lazy do not have a coroutine_handle "
"Maybe the allocation failed or you're using a used Lazy");

// callback should take a single Try<T> as parameter, return value will
// be ignored. a detached coroutine will not suspend at initial/final
// suspend point.
Expand Down Expand Up @@ -474,7 +485,9 @@ class [[nodiscard]] Lazy : public detail::LazyBase<T, /*reschedule=*/false> {
// via() called.
RescheduleLazy<T> via(Executor* ex) && {
logicAssert(this->_coro.operator bool(),
"Lazy do not have a coroutine_handle");
"Lazy do not have a coroutine_handle "
"Maybe the allocation failed or you're using a used Lazy");

this->_coro.promise()._executor = ex;
return RescheduleLazy<T>(std::exchange(this->_coro, nullptr));
}
Expand All @@ -485,12 +498,17 @@ class [[nodiscard]] Lazy : public detail::LazyBase<T, /*reschedule=*/false> {
// for internal purpose only. See uthread/Await.h/await for details.
Lazy<T> setEx(Executor* ex) && {
logicAssert(this->_coro.operator bool(),
"Lazy do not have a coroutine_handle");
"Lazy do not have a coroutine_handle "
"Maybe the allocation failed or you're using a used Lazy");
this->_coro.promise()._executor = ex;
return Lazy<T>(std::exchange(this->_coro, nullptr));
}

auto coAwait(Executor* ex) {
logicAssert(this->_coro.operator bool(),
"Lazy do not have a coroutine_handle "
"Maybe the allocation failed or you're using a used Lazy");

// derived lazy inherits executor
this->_coro.promise()._executor = ex;
return typename Base::ValueAwaiter(std::exchange(this->_coro, nullptr));
Expand Down Expand Up @@ -527,7 +545,8 @@ class [[nodiscard]] RescheduleLazy

RescheduleLazy<T> setLazyLocal(void* lazy_local) && {
logicAssert(this->_coro.operator bool(),
"Lazy do not have a coroutine_handle");
"Lazy do not have a coroutine_handle "
"Maybe the allocation failed or you're using a used Lazy");
this->_coro.promise()._lazy_local = lazy_local;
return RescheduleLazy<T>(std::exchange(this->_coro, nullptr));
}
Expand All @@ -551,6 +570,45 @@ inline Lazy<void> detail::LazyPromise<void>::get_return_object() noexcept {
return Lazy<void>(Lazy<void>::Handle::from_promise(*this));
}

/// Why do we want to introduce `get_return_object_on_allocation_failure()`?
/// Since a coroutine will be roughly converted to:
///
/// ```C++
/// void *frame_addr = ::operator new(required size);
/// __promise_ = new (frame_addr) __promise_type(...);
/// __return_object_ = __promise_.get_return_object();
/// co_await __promise_.initial_suspend();
/// try {
/// function-body
/// } catch (...) {
/// __promise_.unhandled_exception();
/// }
/// co_await __promise_.final_suspend();
/// ```
///
/// Then we can find that the coroutine should be nounwind (noexcept) naturally
/// if the constructor of the promise_type, the get_return_object() function,
/// the initial_suspend, the unhandled_exception(), the final_suspend and the
/// allocation function is noexcept.
///
/// For the specific coroutine type, Lazy, all the above except the allocation
/// function is noexcept. So that we can make every Lazy function noexcept
/// naturally if we make the allocation function nothrow. This is the reason why
/// we want to introduce `get_return_object_on_allocation_failure()` to Lazy.
///
/// Note that the optimization may not work in some platforms due the ABI
/// limitations. Since they need to consider the case that the destructor of an
/// exception can throw exceptions.
template <typename T>
inline Lazy<T>
detail::LazyPromise<T>::get_return_object_on_allocation_failure() noexcept {
return Lazy<T>(typename Lazy<T>::Handle(nullptr));
}

inline Lazy<void>
detail::LazyPromise<void>::get_return_object_on_allocation_failure() noexcept {
return Lazy<void>(Lazy<void>::Handle(nullptr));
}
} // namespace coro
} // namespace async_simple

Expand Down

0 comments on commit fe847c3

Please sign in to comment.