co_await needs an after-resume check
TL;DR: Y y = co_await(???) f(x);
should unroll into
auto e = f(x);
if (!e.await_ready()) {
... suspend ...
if (e.await_suspend(h)) return;
resume:
... resume ...
}
if (???) co_return Cancelled{}; // <<< THIS
Y y = e.await_resume();
because otherwise I have to write boilerplate.
Here come the walls of code
Often we cannot write code like this
class C {
X x_;
Y y_;
Future<int> coro() {
x_ = co_await f();
y_ = co_await g();
co_return 42;
}
};
because *this
could be destroyed while the coroutine is suspended.
So we use weak_ptr
class C : public enable_shared_from_this<C> {
X x_;
Y y_;
Future<int> coro() {
auto weak_this = weak_from_this();
X tmp_x = co_await f();
auto this_pin = weak_this.lock();
if (!this_pin) co_return Cancelled{};
x_ = std::move(tmp_x);
this_pin.reset(); // Don't hold the pin while the coroutine is suspended.
Y tmp_y = co_await g();
this_pin = weak_this.lock();
if (!this_pin) co_return Cancelled{};
y_ = std::move(tmp_y);
co_return 42;
}
};
Or we could use a completely different liveness check, but in the end it would be something like
Future<T> coro() {
auto is_alive = ... this ...; // Add the liveness indicator to the coroutine state.
X tmp_x = co_await f();
if (... !is_alive ...) // Check is |this| is still valid. {
// Return something else than |T| in |Future<T>|.
co_return Cancelled{};
}
x_ = std::move(tmp_x);
}
Theoretically, we could do this with await_transform
and exceptions:
template<typename T>
class SpecialFuture {
class promise_type {
std::function<bool()> is_alive;
std::shared_ptr<void> pin;
template<typename C>
auto await_transform(weak_ptr<C> weak_ptr) {
is_alive = [=]{
pin = weak_ptr.lock();
return !!pin;
};
return suspend_never{};
}
struct Cancel{};
template<typename F>
auto await_transform(F future) {
struct wrapper {
F future;
promise_type* promise;
auto await_ready() { return future.await_ready(); };
auto await_suspend(coroutine_handle<> h) {
promise->pin.reset(); // Don't hold the pin while the coroutine is suspended.
return future.await_suspend(h);
};
auto await_resume() {
if (!promise->is_alive()) throw Cancel{};
return future.await_resume();
}
};
return wrapper{future, this};
}
void unhandled_exception() {
try {
throw;
} catch (Cancel) {
// OK
} catch (...) {
terminate();
}
}
};
};
class C : public enable_shared_from_this<C> {
X x_;
Y y_;
SpecialFuture<int> coro() {
co_await weak_from_this(); // Initialize |p.is_alive|.
x_ = co_await f();
y_ = co_await g();
co_return 42;
}
};
But exceptions are not allowed in certain projects, and I wouldn't recommend to replace return
with throw
anyways.
And I have no idea how it should look like syntax-wise.