owned this note changed a year ago
Published Linked with GitHub

Reading notes: A Mechanism for Async Cancellation

2023-11-16: "Reading Club / Open Discussion: Async Cancellation" #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:

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 ("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.

Select a repo