GorNishanov / coroutines-ts

20 stars 2 forks source link

Can we add a core language mechanism for determining whether or not a coroutine is suspended? #9

Closed brycelelbach closed 7 years ago

brycelelbach commented 7 years ago

Hey Gor!

A lot of the interfaces in coroutine_handle have a precondition requiring that the coroutine they are referring to is suspending. However, we currently have no mechanism for checking if a coroutine is suspended - at least, there's nothing in the coroutine_handle API and I couldn't find any intrinsic in either LLVM or Clang to do this.

It would be really nice to have this functionality. Consider:

void f(coroutine_handle<SomeAwaitable> h)
{
  h.resume(); // How does the author of f check that h is suspended and that this isn't UB if they want to?
}

I have a library solution as an interim fix: https://gist.github.com/brycelelbach/f598a9bd091de8b0e8bfddd86ddd5d57.

You can use the promise method of coroutine_handle to get the underlying Awaitable, e.g.

void f(coroutine_handle<suspend_status<SomeAwaitable>> h)
{
  if (h.promise().is_suspended())
    h.resume();
}

Pinging @efcs who has an interest in this as well.

GorNishanov commented 7 years ago

There is no intrinsic, because it is impossible to figure it out in the general case without adding an overhead. Also, as soon as you asked, the information can be stale as a different thread can come in and destroy or resume the coroutine, so, you would probably want some locking as well.

The intent of the Coroutine TS is for the compiler provide the most efficient coroutines possible and for the libraries to wrap them in the safe abstractions. By design, coroutine_handle is a sharp instrument that should be used with care.

That does not preclude some vendors having a debug implemenations that do some extra checking when possible.

brycelelbach commented 7 years ago

Here's what I came up with:

// track_suspend: A higher-order Awaitable which tracks the status of the
// associated coroutine. Useful because we currently don't have core language
// support for checking the state of a coroutine and many methods of
// coroutine_handle will exhibit UB if the coroutine they refer to is not
// suspended.

template <typename Awaitable>
struct track_suspend
{
  private:
    Awaitable a;
    bool state; // False if suspended and true otherwise.

  public:
    track_suspend() noexcept(noexcept(Awaitable{})) : a{}, state{true} {}

    constexpr auto await_ready() noexcept(noexcept(a.await_ready()))
    {
        // Coroutines TS 5.8.3 [expr.await] p5.1 states "the await-expression
        // evaluates the await-ready expression, then if the result is false,
        // the coroutine is considered suspended. Then, the await-suspend
        // expression is evaluated". I intrepret this to mean that we become
        // suspended here, not in await_suspend. 
        return state = a.await_ready();
    }

    // This has to be templated in case the wrapped Awaitable takes
    // coroutine_handle<Promise> instead of coroutine_handle<>.
    template <typename Promise>
    constexpr auto await_suspend(coroutine_handle<Promise> h)
        noexcept(noexcept(a.await_suspend(h)))
    {
        return a.await_suspend(h);
    }

    constexpr void await_resume() noexcept(noexcept(a.await_resume()))
    {
        state = true;
        a.await_resume();
    }

    constexpr bool is_suspended() const noexcept
    {
        return !state;
    }
};

I stuck it in the little library I'm writing. Seems to do the job!