C++20 Coroutines, await_resume return lvalue, Segmentation fault

Viewed 314

Happening

I want to store move only data to promise_type, and get it in coroutine. I try return return lvalue from await_resume(). The result is compilation is successful but Segmentation fault appears during execution.

How can I improve?


Environment

  • OS: WSL2 Ubuntu 20.04
  • Complier: gcc 10.3
  • Command: g++ -std=c++20 -fcoroutines main.cpp

Code

main.cpp:

#include <iostream>
#include <coroutine>
#include <memory>

struct task
{
    struct promise_type
    {
        using data_type = std::unique_ptr<int>;

        task get_return_object() noexcept
        {
            return task(std::coroutine_handle<promise_type>::from_promise(*this));
        }
        std::suspend_never initial_suspend() const noexcept { return {}; }
        void unhandled_exception() {}
        std::suspend_always final_suspend() const noexcept { return {}; }

        data_type data; // my data
    };

    void set_data(promise_type::data_type&& data) // [2] set my data
    {
        handle.promise().data = std::forward<promise_type::data_type>(data);
    }

    void resume()
    {
        handle.resume();
    }

    task(std::coroutine_handle<promise_type> handle) : handle(handle) {}

private:
    std::coroutine_handle<promise_type> handle;
};

struct awaitalbe
{
    bool await_ready() { return true; }
    void await_suspend(std::coroutine_handle<task::promise_type> handle) { this->handle = handle; }
    task::promise_type::data_type&& await_resume()
    {
        return std::move(handle.promise().data); // [3] get my data
    }

    std::coroutine_handle<task::promise_type> handle;
};

task f()
{
    auto p2 = co_await awaitalbe{}; // [4] Segmentation fault here. after [3]
    std::cout << (*p2) << std::endl;
}

int main(int argc, char* argv[])
{
    auto task1 = f();

    task::promise_type::data_type p1 = std::make_unique<task::promise_type::data_type::element_type>(10);
    task1.set_data(std::move(p1)); // [1] set my data

    task1.resume();
    return 0;
}

1 Answers

In your struct awaitable, you have:

bool await_ready() { return true; }

To clarify, await_ready is a mechanism used to determine whether the result corresponding to an awaitable is ready or not. If it is ready, then there is really no good reason to suspend the calling coroutine (i.e the one that co_awaits the awaitable) and wait for the result - just call await_resume which retrieves the result.

It allows you do something like:

bool await_ready() {
  if (will_read_block()) {
     return false;
  }
  read_some_data_from_file();
  return true;
}

so that non-blocking reads will complete synchronously.

In your case, you always return true. This tells the runtime: when a coroutine awaits an awaitable, never suspend that coroutine and directly call await_resume, skipping the suspension and await_suspend altogether. Indeed, if you set a breakpoint in await_suspend (or use a print statement if your debugger does not like coroutines) in await_suspend, you will find out that it never gets called. Therefore, you never actually assign anything to the handle member in your awaitable struct, resulting in undefined behaviour when you attempt to get a promise out of it.

The solution: just return false from await_ready.

Related