How would you implement coroutine cancellation?

119 views
Skip to first unread message

Vanegicloh J

<vanegicloh@gmail.com>
unread,
Oct 14, 2023, 2:52:54 PM10/14/23
to seastar-dev
Hello.
When I launch a fiber, I want to be able to stop it in some moment. Seastar has abort_source - a facility to cooperative check and abort task execution. But I don't want to check manually for abort request every time. Also, passing it to every function will make API worse. 

Is there a pretty way to hide abort_source from user inside the coroutine frame? There is an example in WInRt. They hide cancellation token inside coroutine really nice: https://devblogs.microsoft.com/oldnewthing/20230203-00/?p=107790
Also, in Go there is also a runtime Context feature to cancel goroutine execution.
Is it possible to do in seastar? How would you implement it?

P.s. my current idea is to pass abort_source ref to a coroutine frame and check is_abort_requested flag every time await_resume() is invoked. If abort is requested then throw a specific exception.

Thank you!

Kefu Chai

<kefu.chai@scylladb.com>
unread,
Oct 15, 2023, 10:33:23 AM10/15/23
to seastar-dev
hi Vanegicloh,

On Sunday, October 15, 2023 at 2:52:54 AM UTC+8 Vanegicloh J wrote:
Hello.
When I launch a fiber, I want to be able to stop it in some moment. Seastar has abort_source - a facility to cooperative check and abort task execution. But I don't want to check manually for abort request every time. Also, passing it to every function will make API worse. 

Is there a pretty way to hide abort_source from user inside the coroutine frame? There is an example in WInRt. They hide cancellation token inside coroutine really nice: https://devblogs.microsoft.com/oldnewthing/20230203-00/?p=107790
Also, in Go there is also a runtime Context feature to cancel goroutine execution.
Is it possible to do in seastar? How would you implement it?

i also studied the cancellable coroutines implemented by folly and C++/WinRT. both of them implement the cancellable coroutine. i believe there should be an elegant way to implement a similar cancellable coroutine in Seastar. but i am afraid it won't be trivial. some rough ideas:

1. we will have a cancellable token which is populated to the the coroutines created by their parent coroutine. the cancellable token should be stored in `task` (or `cancellable_task` ?)
2. the internal state is shared by the outer most caller and all the coroutines on the leafs. assuming we need to cancel the subcoroutines created by calls like `when_all()`, which creates multiple coroutines and run them in parallel.
3. the source of the cancellation need to explicitly created by the caller of the cancellable coroutine.
3. ideally, the cancel operation can be optionally populated all the way down to the reactor backend. please note, io_uring can cancel a previously submitted request. so all low-level ops will need to have their own cancellable variants.
4. a new coroutine type which is different from the existing one will be created. probably we don't need to create a parallel series of coroutine types, we could use CPO to let the coroutines to opt-in, so they can customize 1)  how the token is passed down, 2) how the cancellation is performed. sometimes, if we can just close an fd or, in io_uring, submit a cancellation request . so a callback is more capable in this case.
 

P.s. my current idea is to pass abort_source ref to a coroutine frame and check is_abort_requested flag every time await_resume() is invoked. If abort is requested then throw a specific exception.

i am not sure how await_resume is able to access a variable stored in the coroutine frame. do you mean storing it in the `promise_type`? if that's the case, it's quite similar to what i suggest above: to store it in the task. but i prefer storing the token.

anyway, IMHO, this approach is inherently limited. as i think a more capable solution is to allow us to cancel a coroutine even when it is suspended.


Thank you!

Avi Kivity

<avi@scylladb.com>
unread,
Oct 15, 2023, 10:53:51 AM10/15/23
to Vanegicloh J, seastar-dev
In Seastar this is done at the application level. For example, you can pass an abort_source and check on it, or wait for a condition_variable that is signaled when there is work to do or when it is time to terminate.

The reason it's not done at the coroutine or fiber level is that there can be clean-up work to perform. It could be done via destructors, but not in the general case.

Vanegicloh J

<vanegicloh@gmail.com>
unread,
Oct 16, 2023, 2:58:46 AM10/16/23
to seastar-dev

Kefu Chai:

1. we will have a cancellable token which is populated to the the coroutines created by their parent coroutine. the cancellable token should be stored in `task` (or `cancellable_task` ?)

How can we populate token to a child coroutine? Coroutine frame and promise_type know nothing about its parent or child coroutine. Only seastar::promise<> in promise_type could possibly pass some data to its related future, which is stored in parent coroutine after get_return_object() call.
Sorry, i don't have deep knowledge of seastar tasks. Do we have a mechanism to pass data (cancellation_token) from one task to another? Are parent and child tasks have some channel to communicate?
 

Benny Halevy

<bhalevy@scylladb.com>
unread,
Oct 16, 2023, 3:16:04 AM10/16/23
to Avi Kivity, Vanegicloh J, seastar-dev
Especially if the cleanup is asynchronous.  We cannot co_await in destructors.


--
You received this message because you are subscribed to the Google Groups "seastar-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to seastar-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/seastar-dev/084994a1de0cb9301317c500f52a63651f3211ed.camel%40scylladb.com.

Vanegicloh J

<vanegicloh@gmail.com>
unread,
Oct 18, 2023, 7:01:38 AM10/18/23
to seastar-dev
Bump. Could you please answer :)

понедельник, 16 октября 2023 г. в 09:58:46 UTC+3, Vanegicloh J:

Avi Kivity

<avi@scylladb.com>
unread,
Oct 18, 2023, 7:33:28 AM10/18/23
to Vanegicloh J, seastar-dev
You could extend the task class with something like a cancellation token. Such an extension must be well motivated since it introduces overhead to everything in Seastar.



Vanegicloh J

<vanegicloh@gmail.com>
unread,
Oct 19, 2023, 2:51:56 AM10/19/23
to seastar-dev
 seastar-dev:
You could extend the task class with something like a cancellation token. Such an extension must be well motivated since it introduces overhead to everything in Seastar.

Thank you for the answer, but question was about propagation. Let's say i extend task class with a cancellaion_token. How could i propagate it to the another task? 
Parent coroutine creates cancellation_token and passes it to all child coroutines that were launched with co_await.

future buz()
{
    // I want to cancel here. How to propagate cancellation token
    // from foo() to buz() without explicit passing as a function argument?
    co_await sleep(10ms);
}

future<> bar()
{
    co_await buz();
}

future<> foo(cancellation_token& ct)
{
    // co_await propagate_token(ct); ???
    co_await bar()
}

Avi Kivity

<avi@scylladb.com>
unread,
Oct 19, 2023, 4:56:50 AM10/19/23
to Vanegicloh J, seastar-dev
The current_task is stored somewhere. So it could be retrieved from there. It feels fragile to me but I haven't thought it through.


Another option is to pass it explicitly. You can also specialize coroutine_traits for the case where a cancellation token is passed.


Reply all
Reply to author
Forward
0 new messages