-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature] Support for C++20 coroutines #11
Comments
I'm not an expert on coroutines but this will likely not work in general. The error objects in LEAF are stored in a That said, I am exploring the possibility to support coroutines. At a minimum, this would require that upon suspend, all |
I would actually be fine with the usual single-threaded MVP first. Similar to how boost::leaf works in other places that do not use the |
To support your use case, is it sufficient to |
Basically yes, it needs some better type deduction, but to give you an idea, this one compiles for me: handle_errors.hpp template <class TryBlock, class... H>
BOOST_LEAF_CONSTEXPR inline
boost::asio::awaitable<void>
try_handle_all( TryBlock && try_block, H && ... h ) noexcept
{
//static_assert(is_result_type<decltype(std::declval<TryBlock>()())>::value, "The return type of the try_block passed to a try_handle_all function must be registered with leaf::is_result_type");
context_type_from_handlers<H...> ctx;
auto active_context = activate_context(ctx);
if( auto r = co_await std::forward<TryBlock>(try_block)() )
co_return r.value();
else
{
error_id id = r.error();
ctx.deactivate();
//using R = typename std::decay<decltype(std::declval<TryBlock>()())>::type;
co_return ctx.template handle_error<void>(std::move(id), std::forward<H>(h)...);
}
} #include <boost/asio.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/leaf.hpp>
namespace bl = boost::leaf;
namespace asio = boost::asio;
int main()
{
asio::io_context io_context;
asio::co_spawn(
io_context,
[]() noexcept -> boost::asio::awaitable<void> {
bl::try_handle_all(
[]() noexcept -> boost::asio::awaitable<bl::result<void>> { co_return bl::result<void>{}; },
[](const bl::error_info&) {});
co_return ;
},
asio::detached);
io_context.run();
} |
I think this will have to wait I need to do more research about coroutines before I commit to any support. |
I think this should be mentioned in the docs that the library has all this niceness like type erasure and attaching arbitrary data at cost of passing information out of band via global state . Just to make documentation less biased when comparing with outcome (as the latter can be used to pass information between threads conveniently without prior setup: since outcome's result is a value type it can be transferred from one thread to another when needed). With coroutines I think simply adding type are not enough because there can be multiple coroutines in suspended state each with own try blocks so context needs to be stored and restored together with coroutine suspension. |
To clarify, error objects in LEAF are not global; they use automatic storage duration in error-handling scopes (thread-local pointers are used to access the error objects). When necessary, error objects can also be stored in a As for coroutines, it would be nice to avoid the above, which is likely possible by deactivating/activating a |
@zajo First I think becoming a part of Boost is a big thing 👍 Don't get me wrong: I didn't say that relying on globals is bad by definition (yes, I realize that leaf uses TLS but I consider it still global, and I wrote "information" not "error objects"). It's just a design choice, same as with logging: you may pass logger objects around with components and different levels "polluting" API or you may use globals to do this at the right place. Both have own strengths and weaknesses. And my point was to just present them so consumers could make a rational choice. With coroutines it may be reasonable to focus on asio first rather than making a generic coroutine-friendly wrappers. I agree that now there is not much information and all that generic code looks scary and tangled when trying to understand how things work and work together... This guide is worth reading, explains some details about handler <-> completion interaction (if you didn't see it): https://www.boost.org/doc/libs/1_75_0/libs/outcome/doc/html/recipes/asio-integration-1-70.html |
This section of the whitepaper explains that designs that carry error objects in a The use of TLS is an implementation detail. It is incorrect for the documentation to specify implementation details, except as it pertains to portability. See this example for using LEAF with ASIO. |
Edit on 20210329: Adapting the code from #10: #include <asio.hpp>
#include <boost/leaf.hpp>
#include <iostream>
#if defined(BOOST_NO_EXCEPTIONS)
namespace boost
{
void throw_exception(const std::exception &exception) { std::abort(); }
} // namespace boost
#endif // defined(BOOST_NO_EXCEPTIONS)
#if defined(ASIO_NO_EXCEPTIONS)
namespace asio::detail
{
template<typename exception_type>
void throw_exception(const exception_type &exception)
{
boost::throw_exception(exception);
}
} // namespace asio::detail
#endif // defined(ASIO_NO_EXCEPTIONS)
namespace bl = boost::leaf;
int main()
{
asio::io_context io_context;
asio::co_spawn(
io_context,
[&]() noexcept -> bl::awaitable<void> {
std::clog << "try 1\n";
co_await bl::co_try_handle_all(
[&]() noexcept -> bl::awaitable<bl::result<void>> {
asio::steady_timer timer(io_context);
timer.expires_after(std::chrono::milliseconds(500));
std::clog << "before wait 1\n";
co_await timer.async_wait(bl::use_awaitable);
std::clog << "after wait 1\n";
bl::result<void> result = co_await bl::co_new_error(42);
co_return result;
},
[](int i) { std::clog << "error 1 " << i << "\n"; }, [](const bl::error_info &ei, int i) { std::clog << "error 1 " << ei << " " << i << "\n"; },
[](const bl::error_info &ei) { std::clog << "error 1 " << ei << "\n"; });
std::clog << "return 1\n";
co_return;
},
asio::detached);
asio::co_spawn(
io_context,
[&]() noexcept -> bl::awaitable<void> {
std::clog << "try 2\n";
co_await bl::co_try_handle_all(
[&]() noexcept -> bl::awaitable<bl::result<void>> {
asio::steady_timer timer(io_context);
auto result = co_await bl::co_new_error(43);
timer.expires_after(std::chrono::seconds(1));
std::clog << "before wait 2\n";
co_await timer.async_wait(bl::use_awaitable);
std::clog << "after wait 2\n";
co_return result;
},
[](int i) { std::clog << "error 2 " << i << "\n"; }, [](const bl::error_info &ei, int i) { std::clog << "error 2 " << ei << " " << i << "\n"; },
[](const bl::error_info &ei) { std::clog << "error 2 " << ei << "\n"; });
std::clog << "return 2\n";
co_return;
},
asio::detached);
io_context.run();
} |
Thank you! I'll take a closer look later, dealing with an emergency right now. |
No hurry. See the edit above. I'm still scratching my head wondering how this all can eventually be done. |
I got some time to play a bit with leaf lately. |
Thanks, I really appreciate this. I'll try to look at your work soon, but I'm swamped with other work lately. Perhaps we should connect on CPP slack to discuss, you can help me understand what you've done. |
Did anything come of this? I don't see a way to automatically deduce the type for co_return and the error macros don't appear to work within a coroutine. Is there an easier way to handle this with coroutines? |
Searching around the boost repositories, I found Boost Cobalt which appears to provide some sort of adapter for Boost Leaf: https://live.boost.org/doc/libs/1_84_0/libs/cobalt/doc/html/index.html#leaf I haven't tried it yet but I'll slap it here just in case. |
To use this library with C++20 coroutine the call in
handle_error.hpp
-try_handle_all
ortry_handle_some
:if( auto r = std::forward<TryBlock>(try_block)() )
Should check whether the returned time is an Awaitable and use
if( auto r = co_await std::forward<TryBlock>(try_block)() )
instead. Similarly for the error handling functions. This would allow users to write code among the lines of:
which is currently not possible. Maybe we could even achieve less verbosity. Ideally I would want to write
co_return {};
just like before, but I thinkboost::leaf::result<>
would need a constructor that takes aninitializer_list
then.The text was updated successfully, but these errors were encountered: