Closed Lyulf closed 3 months ago
I don't understand the problem. Why don't you just spawn a task and use asio::bind_cancellation_slot
on the completion token?
This is exactly what I was looking for. I knew that it should be simple to achieve but I guess my lack of experience with asio must have been showing.
I guess I was too hasty will closing the issue. After more testing I found that using asio::bind_cancellation_slot
seems to still prevent cancellation which is called directly on a promise.
Here is an example:
#include <cassert>
#include <boost/asio.hpp>
#include <boost/cobalt.hpp>
namespace asio = boost::asio;
namespace cobalt = boost::cobalt;
using namespace std::chrono_literals;
struct SuspendUntilCancelled
{
struct Cancellation
{
cobalt::executor executor;
void* address = nullptr;
void operator()(asio::cancellation_type /*type*/)
{
if (address != nullptr)
asio::post(executor, cobalt::unique_handle<void>(address));
}
};
bool await_ready() { return false; }
template <typename Promise>
void await_suspend(std::coroutine_handle<Promise> handle)
{
auto executor = handle.promise().get_executor();
slot = handle.promise().get_cancellation_slot();
assert(slot.is_connected());
slot.emplace<Cancellation>(executor, handle.address());
}
void await_resume()
{
if (slot.is_connected())
slot.clear();
}
asio::cancellation_slot slot;
};
struct CancellableContainer
{
cobalt::promise<void> suspendUntilCancel()
{
co_await cobalt::spawn(co_await cobalt::this_coro::executor,
suspend(),
asio::bind_cancellation_slot(
signal.slot(), cobalt::use_op)); // using just cobalt::use_op works just fine
}
void cancel() { signal.emit(asio::cancellation_type::all); }
private:
cobalt::task<void> suspend() { co_await SuspendUntilCancelled{}; }
asio::cancellation_signal signal;
};
cobalt::main co_main(int /*argc*/, char** /*argv*/)
{
CancellableContainer container;
auto promise = container.suspendUntilCancel();
//container.cancel(); // works correctly
promise.cancel();
co_await promise; // permanently suspended if cancelled using promise.cancel()
co_return 0;
}
In the above code you're intercepting the cancel
on the promise. That is the co_await spawn
doesn't get the cancel of the promise forwarded.
Then is there a way to allow cancellation from both cobalt::promise
and asio::cancellation_signal
or should I write something on my own?
You'd need to write it on your own. You can get the cancellation_state as describe here.
Ok. Thank you for your help!
I have coroutines that need to be cancelled in order for me to correctly handle cleanup of a class. I have tried using
reset_cancellation_source
directly onpromise_type
(sincecobalt::this_coro::reset_cancellation_source
doesn't work outsidecobalt::detached
) but cancellation signal is ignored if sent before cancellation source is reset and cancellation stops working when called directly on the coroutine (e.g. fromcobalt::race
).I can probably work around those problems but I was wondering if there is a built in way to do this? If not then are there any plans for adding that in the future?
Here is an example code to hopefully demonstrate what I want to achieve (sorry for the length):