--- title: "Reading notes: A Mechanism for Async Cancellation" tags: WG-async, reading-club, minutes date: 2023-11-16 discussion: url: https://hackmd.io/TbDc4RWCSkGEketBWMLmfA --- # Reading notes: A Mechanism for Async Cancellation 2023-11-16: "Reading Club / Open Discussion: Async Cancellation" [#323](https://github.com/rust-lang/wg-async/issues/323) Link to document: https://theincredibleholk.org/blog/2023/11/14/a-mechanism-for-async-cancellation/ ## Attendance - People: TC, eholk, tmandry, swapna iyer - Minutes: TC ## Return value of `poll_cancel` TC: This is a nit, but I'd expect `poll_cancel` to return a non-unit value (of a different type than `poll`). So the signature would be: ```rust pub trait Future { type Output; type CancelOutput = (); // Needs a new feature. fn poll(self: Pin<&mut Self>, cx: Context) -> Poll<Self::Output>; fn poll_cancel(self: Pin<&mut Self>, cx: Context) -> Poll<Self::CancelOutput> { Poll::Ready(()) // Needs new magic. } } ``` One often needs to know, e.g., whether cancellation happened cleanly, whether a timeout or abort happened, whether some rollback was not possible, etc. eholk: There's definitely a use case for that, but I think it's also kind of at odds with using `poll_cancel` as a backing mechanism for `async Drop`, since destructors don't return values currently. (Although maybe they should so we can have fallible destructors too?) TC: Good point. This may be one of those areas where async drop and async cancellation are in fact two different things. tmandry: Had the same thought. Going in the linear types direction may be another option. eholk: I really like the "destructors by convention" that you could do if we had a linear type system. TC: +1. yosh: -1. I don't think we should go that direction. I think of async as a "portability across domains" mechanism. A kind of portability across effects. So cancellation APIs are a part of that. But the core of it should feel like the same language. TC: My feeling is that the linear types concepts that we're discussing here would cut across both sync and async. tmandry: +1. I see these as orthogonal. We could have both. We could have async drop, and we could have the mechanism with explicit destructuring. yosh: Guaranteed drop and "must use" are maybe two separate things we should distinguish. tmandry: There is `?Leak`, a type that cannot be leaked, and there is also `?Drop`, and we might want them both. We'd have to think about how painful the transition would be. eholk: I think these are different. Linear types would be really valuable for sync code also. They give a lot of power that we currently put into destructors just because we don't have a better way to do it right now. With linear types, destructors become more of a convenience. ## Can we really block during unwinding? tmandry: The post essentially proposes that we `block_on` a future that hasn't finished cancellation during unwinding. Aren't there issues with `block_on` that could lead to deadlocks? And if we could do this during unwinding, why couldn't we also do it during `drop` of the future itself? eholk: I didn't work this idea out fully, but my idea is to basically use `catch_unwind` in the `await` desugaring and then use `resume_unwind` after `poll_cancel` is finished, so we'd be able to suspend during unwinding (which I'll admit sounds absolutely terrifying). tmandry: I've previously thought about how we could do something with poisoning here. yosh: This could make for a really good use case for `#[no_panic]` functions. yosh: Separately, could we do something more efficient if we knew that the futures were all on the same thread? eholk: Even if everything is on the same thread you could still get deadlocks. tmandry: If you think of a tree of futures, each branch represents a thread, so in the analogy with mutexes, each branch can be poisoned. yosh: Cool, thank you! ## Recursive cancellation TC: In the first post it seemed that you were leaning toward recursive cancellation. What are your thoughts about implementing that under this model? eholk: I'd like to do an experiment that supports recursive cancellation. It does seem strictly more powerful, but that power might not be useful. TC: More power of this kind is almost always useful. tmandry: You could do this by returning a future from cancel, or by signaling it. eholk: There are some challenges with returning a future, due to types and in particular lifetimes, but it does solve a problem with e.g. cancellation on a vec. tmandry: This is an area where (hand wave) async Drop would be useful. eholk: ...maybe we could implement IntoCancellableFuture on Vec. yosh: This is the async-sync-async sandwich but in terms of object lifetimes instead of function calls. yosh: alternative solution - return a future but mark it as an [`AtomicFuture`](https://blog.yoshuawuyts.com/async-iterator-trait/#cancellation-safety) ("cancellation-safe future"). ## Code size yosh: When I was reasoning through how to implement async Drop, I was worried about the code size - potentially being unbounded. But it seems it's no longer unbounded - just big. Have you measured what the code size overhead is? yosh: To clarify - I think this will be worth it. I just want to make sure we have a sense of the generated code size. eholk: I haven't looked into code size at all. I think it is a finite explosion, but finite can still be quite large.