4

I am trying out the C++20 coroutines with boost asio. My current intention is to embed in the coroutine example from https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/example/cpp17/coroutines_ts/echo_server.cpp a simple suspension point.

As far as I understand the documentation here https://en.cppreference.com/w/cpp/coroutine/suspend_always, this call shall be valid:

co_await suspend_always{};

inside this coroutine:

awaitable<void> echo(tcp::socket socket)
{
  try
  {
    char data[1024];
    for (;;)
    {
      std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data), use_awaitable);
      co_await suspend_always{};  // <-- here
      co_await async_write(socket, boost::asio::buffer(data, n), use_awaitable);
    }
  }
  catch (std::exception& e)
  {
    std::printf("echo Exception: %s\n", e.what());
  }
}

However, there is a compiler error:

error: no matching member function for call to 'await_transform'
            co_await suspend_always{};
            ^~~~~~~~

Can someone please explain, how to introduce a suspension point into the above coroutine without using a timer with an async_wait.

Barry
  • 286,269
  • 29
  • 621
  • 977
divot53305
  • 61
  • 3

3 Answers3

2

There is not such thing as "a simple suspension point" in C++ coroutines. Suspension points are internal machinery of coroutines and how suspension is used by the coroutine provider (author of a concrete coroutine library) is implementation detail of concrete instance of coroutine provider (asio awaitable in your example). If coroutine provider does not expose API for adding user defined extensions for provider's coroutines there is nothing that you can do about it.

In your example, you want to add a suspension point. But who will be responsible for resuming the coroutine? And how such resumption will be performed? Will such unexpected suspension brake coroutine provider's expectations?

Maybe it is not obvious that such actions should not be done without the knowledge of implementation details of coroutine provider or a sanction from that coroutine provider, so let us consider simple example - generator.

Generators usually are implemented as follows: class generator is the return value of coroutine. It has pointer to coroutine handle. Generator provides begin() and end() member functions that returns current iterator and sentinel. When incrementing an iterator coroutine handle resumes, runs and either stores next value in designated storage or runs to completion. When iterator is being compared with sentinel coroutine handle is checked for completion (so iterator current is equal to end when coroutine is completed). When iterator is dereferenced it returns stored value from storage.

You can use such generator like this:

generator example() {
  co_yield 1;
  co_yield 2;
  co_yield 3;
}

generator g = example(); // create generator

// iterate over every value
for(auto i : g) { 
  std::cout << i << '\n';
}
// prints:
// 1
// 2
// 3

Then let's pretend that user was able to add suspension point to example function:

generator example2() {
  co_await suspend_always{}; // simple suspension point
  co_yield 1;
  co_yield 2;
  co_yield 3;
}

It is pretty obvious that now generator's invariants are broken. Coroutine is suspended but no value was stored for iterator to return.

That is why there need to be an explicit support from the coroutine provider for adding extensions for the coroutine library.

Serikov
  • 1,159
  • 8
  • 15
0

Besides the design consideration mentioned by @Serikov, the implementation cause is that:

the promise_type provides member function await_transform,

which means the awaitable in co_await awaitable must go through function await_transform, according to the C++ standard.

But the asio doesn't provide a compatible overload for std::suspend_always. See boost/asio/impl/awaitable.hpp

I suspect, if you provide a overload for std::suspend_always, it will work as you expected.

auto await_transform(std::suspend_always) noexcept { return std::suspend_always{}; }
Steven Sun
  • 141
  • 6
0

by checking the await_transform overloads mentioned by @steven-sun, code below works as expected.

co_await this_coro::executor;

qduyang
  • 353
  • 1
  • 8
  • No, the `bool await_ready() const noexcept` method of the `auto await_transform(this_coro::executor_t) noexcept` result will always return true instead of false. Therefore, it immediately resumes. – Fabian Keßler Oct 04 '22 at 15:24