Using cppcoro and ASIO's co_spawn together
Asked Answered
Q

1

8

I've a library is written using cppcoro and wish to use it with ASIO. But whenever I try to co_spawn a coroutine from said library. Boost complain that the awaitable type isn't correct.

For example:

#include <asio/io_context.hpp>
#include <asio/coroutine.hpp>
#include <asio/co_spawn.hpp>
#include <asio/detached.hpp>
#include <asio/awaitable.hpp>
#include <cppcoro/task.hpp>

cppcoro::task<int> foo()
{
        co_return 1;
}

int main()
{
        asio::io_context ctx;
        asio::co_spawn(ctx, foo(), asio::detached);
        ctx.run();
}

Boost complains that cppcoro::task<int> isn't derived from asio::awaitable

asio_cppcoro.cpp: In function ‘int main()’:
asio_cppcoro.cpp:15:43: error: no matching function for call to ‘co_spawn(asio::io_context&, cppcoro::task<int>, const asio::detached_t&)’
   15 |  asio::co_spawn(ctx, foo(), asio::detached);
      |                                           ^
In file included from /usr/include/asio/co_spawn.hpp:467,
                 from asio_cppcoro.cpp:3:
/usr/include/asio/impl/co_spawn.hpp:199:1: note: candidate: ‘template<class Executor, class T, class AwaitableExecutor, class CompletionToken>  requires  completion_token_for<CompletionToken, void()> auto asio::co_spawn(const Executor&, asio::awaitable<T, AwaitableExecutor>, CompletionToken&&, typename std::enable_if<((asio::is_executor<Executor>::value || asio::execution::is_executor<T>::value) && std::is_convertible<Executor, AwaitableExecutor>::value)>::type*)’
  199 | co_spawn(const Executor& ex,
      | ^~~~~~~~
/usr/include/asio/impl/co_spawn.hpp:199:1: note:   template argument deduction/substitution failed:
asio_cppcoro.cpp:15:43: note:   ‘cppcoro::task<int>’ is not derived from ‘asio::awaitable<T, AwaitableExecutor>’
   15 |  asio::co_spawn(ctx, foo(), asio::detached);

I also tried to wrap my coroutine in a asio::awaitable with no success.

asio::awaitable<int> bar()
{
        co_return co_await foo();
}

The compiler complains that it cannot wrap the two type together

❯ c++ asio_cppcoro.cpp -o asio_cppcoro -std=c++20 -fcoroutines
asio_cppcoro.cpp: In function ‘asio::awaitable<int> bar()’:
asio_cppcoro.cpp:15:25: error: no matching function for call to ‘asio::detail::awaitable_frame<int, asio::execution::any_executor<asio::execution::context_as_t<asio::execution_context&>, asio::execution::detail::blocking::never_t<0>, asio::execution::prefer_only<asio::execution::detail::blocking::possibly_t<0> >, asio::execution::prefer_only<asio::execution::detail::outstanding_work::tracked_t<0> >, asio::execution::prefer_only<asio::execution::detail::outstanding_work::untracked_t<0> >, asio::execution::prefer_only<asio::execution::detail::relationship::fork_t<0> >, asio::execution::prefer_only<asio::execution::detail::relationship::continuation_t<0> > > >::await_transform(cppcoro::task<int>)’
   15 |  co_return co_await foo();
      |                         ^
In file included from /usr/include/asio/awaitable.hpp:129,
                 from /usr/include/asio/co_spawn.hpp:22,
                 from asio_cppcoro.cpp:3:
/usr/include/asio/impl/awaitable.hpp:150:8: note: candidate: ‘template<class T> auto asio::detail::awaitable_frame_base<Executor>::await_transform(asio::awaitable<T, Executor>) const [with T = T; Executor = asio::execution::any_executor<asio::execution::context_as_t<asio::execution_context&>, asio::execution::detail::blocking::never_t<0>, asio::execution::prefer_only<asio::execution::detail::blocking::possibly_t<0> >, asio::execution::prefer_only<asio::execution::detail::outstanding_work::tracked_t<0> >, asio::execution::prefer_only<asio::execution::detail::outstanding_work::untracked_t<0> >, asio::execution::prefer_only<asio::execution::detail::relationship::fork_t<0> >, asio::execution::prefer_only<asio::execution::detail::relationship::continuation_t<0> > >]’
  150 |   auto await_transform(awaitable<T, Executor> a) const
      |        ^~~~~~~~~~~~~~~
/usr/include/asio/impl/awaitable.hpp:150:8: note:   template argument deduction/substitution failed:
asio_cppcoro.cpp:15:25: note:   ‘cppcoro::task<int>’ is not derived from ‘asio::awaitable<T, asio::execution::any_executor<asio::execution::context_as_t<asio::execution_context&>, asio::execution::detail::blocking::never_t<0>, asio::execution::prefer_only<asio::execution::detail::blocking::possibly_t<0> >, asio::execution::prefer_only<asio::execution::detail::outstanding_work::tracked_t<0> >, asio::execution::prefer_only<asio::execution::detail::outstanding_work::untracked_t<0> >, asio::execution::prefer_only<asio::execution::detail::relationship::fork_t<0> >, asio::execution::prefer_only<asio::execution::detail::relationship::continuation_t<0> > > >’
   15 |  co_return co_await foo();
      |  

Is there a way to call a cppcoro::task<> based coroutine from ASIO's io_context? Also, why is this happning? AKAIF C++ coroutines are stackless so spawning a new stack frame for any coroutine implementaion should be possible. Why does ASIO stop me from doing it?

Compiler: GCC 10.2 ASIO: 1.18.1 (Not Boost ASIO. This is the standalone version)

Quenelle answered 2/5, 2021 at 3:22 Comment(0)
W
4

Unfortunately, asio awaitables and cppcoro awaitables could not be co_await'ed by each other. This happens because asio awaitables require cooperation between caller and callee to coordinate shutdown/ownership. That's why asio awaitables do not satisfy the cppcoro Awaitable concept. There is an issue on github that explains this problem in detail: https://github.com/lewissbaker/cppcoro/issues/131

Nevertheless, you can use asio with cppcoro by providing coroutine wrapper for asio. For this, you can use callback approach for asio, and provide cppcoro::single_consumer_event to the handler, and call event.set() in there.

Here is an example: https://gist.github.com/isergeyam/ce10bef00abfaee3d0ec7f31a21c4b95

Woodland answered 24/6, 2021 at 17:4 Comment(4)
Ah! That's why and think you! Is there a way to do the reverse? Calling cppcoro from ASIO awaitable?Quenelle
I don't think it's possible to use cppcoro coroutines from asio awaitables out of the box, because asio coroutine library does not provide some synchronization mechanism, that could be used to return from cppcoro's context to asio context, as it's done by cppcoro::single_consumer_event. But maybe it could be written manually.Woodland
is it possible to create an own custom completion token, like asio::use_awaitable for cppcoro tasks?Fiscus
@Fiscus yes, you just need to provide a specialization of asio::async_result for your custom completion token. I've done so for my library TooManyCooks (shameless plug) here: github link but it shouldn't be too hard to port this to any other C++20 coroutine library - you mostly need to modify aw_asio_base::callback::operator() to send the coroutine to the correct executor for resumption.Nertie

© 2022 - 2024 — McMap. All rights reserved.