Skip to content
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

Why the support for cancellation is removed? #27

Closed
rzbdz opened this issue Apr 3, 2022 · 8 comments
Closed

Why the support for cancellation is removed? #27

rzbdz opened this issue Apr 3, 2022 · 8 comments

Comments

@rzbdz
Copy link

rzbdz commented Apr 3, 2022

I found that one of the very old versions have a file promise.hpp and it supports cancellation but was removed later.

@CarterLi
Copy link
Owner

CarterLi commented Apr 3, 2022

Because it was slow and greatly complicated the implementation. I was planning implement a variation based on stop_token but I was busy on other things.

@CarterLi CarterLi closed this as completed Apr 3, 2022
@CarterLi
Copy link
Owner

CarterLi commented Apr 3, 2022

@rzbdz I have seen your implementation. However since your task is lazy ( you can't start a task without waiting for its completion ), I don't think your cancel operation is very useful.

@rzbdz
Copy link
Author

rzbdz commented Apr 3, 2022

You are right.
Actually I didn't know how to implement one and the one you mention is indeed useless now.
I am just wandering around to see if someone else have a solution 👀

@CarterLi
Copy link
Owner

CarterLi commented Apr 3, 2022

Because it was slow and greatly complicated the implementation. I was planning implement a variation based on stop_token but I was busy on other things.

Well, the problem is that people usually want their tasks getting cancelled ( clean up the context ) when something bad happens. For example when an exception is thrown, cancel all pending tasks in destructors.

However, cancellation for io_uring is also async, which is problematic because destructors in C++ cannot be a coroutine. In addition, cancellation for io_uring won't always succeed, which is even more problematic because people usually want to free the buffers provided for read / recv. If a task is not successfully cancelled but the buffer allocated for the task is freed, UB will happen.

That makes cancellation for io_uring is very hard to use in C++. I don't find a solution yet.

@CarterLi
Copy link
Owner

CarterLi commented Apr 3, 2022

You are right. Actually I didn't know how to implement one and the one you mention is indeed useless now. I am just wandering around to see if someone else have a solution 👀

IMHO laziness coroutines are pretty much useless because laziness coroutines cannot be run in parallel. If we must wait for every io task's completion immediately, why not use the old sync version ( read(2) / accept4(2) ) directly? io_uring is built for parallel and laziness coroutines make the async interface of io_uring meaningless.

Yes we have when_all when_any, but

  1. you can't implement when_* with lazy coroutines.
  2. you can't detach a coroutine. Imaging you have a server pending for connections. You want to create independent coroutines for every client.
while (true) {
    auto clientfd = co_await accept(serverfd);
    /* no co_await */ ([clientfd]() {
        // sub coroutine for clientfd
        co_await recv(clientfd, buf);
        // handle buf
    })();
}

Since sub coroutine won't block the main coroutine, we can still accept for new connection when waiting for IO tasks in sub coroutine. With laziness coroutines I don't know how I can do that.

@rzbdz
Copy link
Author

rzbdz commented Apr 4, 2022

you can't detach a coroutine. Imaging you have a server pending for connections. You want to create independent coroutines for every client.

And you are right, it make sense. I will take a look if I really need laziness task.

For now, I think async_scope and spawn semantic (together with an executor concept or something like co_spawn)(https://github.com/lewissbaker/cppcoro/blob/master/include/cppcoro/async_scope.hpp) would do what you describe, and the spawn could be supported by io_context too (not a good design though since the io_context handle need to be caught everytime...) , just bring another function call.

An example would be like this:

task<> echo(...){
  // sub coroutine for clientfd
  co_await recv(clientfd, buf);
  // handle buf
}
task<void> listener()
{
  auto exec = something::get_executor_on_this_thread();
  for (;;)
  {
    auto socket = co_await async_accept();
    // what co_spawn does is simply
    // do sth like:
    //     []()->not_lazy<>{ co_await echo(...); } ();
    // where not_lazy<> won't block, 
    // instead detached from current coroutine
    // Or do more thing you want to, 
    // maybe run on another thread etc..
    co_spawn(exec, echo(std::move(socket)));
  }
}

I think one of the benefits laziness task brings is that you can move coroutine to another thread, say if a coroutine want to use another io_service/io_context or some thread_local resources on another thread, laziness task helps.

It's like something more general but roundabout , you can use it as if it's lazy or not lazy with co_spawn sematic. Though not very useful.

Maybe I should make the task not lazy anymore and introduce another lazy<> coroutine.

Thank you for a detailed explanation.

@rzbdz
Copy link
Author

rzbdz commented Oct 11, 2022 via email

@CarterLi
Copy link
Owner

We now have sync cancel

History: axboe/liburing#608

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants