tokio-rs / tokio

A runtime for writing reliable asynchronous applications with Rust. Provides I/O, networking, scheduling, timers, ...
https://tokio.rs
MIT License
26.59k stars 2.45k forks source link

Alternative to AsyncDrop #6615

Closed jxdabc closed 4 months ago

jxdabc commented 4 months ago

This is inspired by all the hard work of @Matthias247.

Background

AsyncDrop is a feature that is very needed in many scenarios. Among them, the widely discussed ones include:

On the other hand, AsyncDrop is HARD to design and never reaches its RFC state, since 2019.

Serious CHALLENGES include, e.g., how to forbid cancelling of async-drops, how to prevent access to an in-progress dropping struct. And the async WG goal is to do those without any support of runtimes, which makes it more challenge.

The final design, if we managed to get there, would probably be either imperfect or complex.

Discussion

There is yet an alternative to AsyncDrop, which makes clean-up work async.

The idea is make futures able to OPT IN to:

The specific goal is:

Surprisingly, when come to the design, it is rather intuitively straight forward. It requires nothing magic or complex, and no compiler support is ever needed. That makes me believe it is probably a right approach to async cancellation.

Design

On the CANCEL-EE (typically an "atomic" Future) side, it needs an API to know it is canceled.

The shape would be:

fn is_cancelled() -> bool

The intended usage would be:

impl Future for SomeFuture {
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if is_cancelled() { 
            // transition the state machine to the CANCELING state
        }
        // ...state machine work
    }
}

On the CANCEL-ER side (typically a scheduler, or some Future combiner, such as select), it needs an API to tell its descendants about the cancellation, and poll them if necessary.

The shape would be:

fn with_cancel_state<F: FnOnce()>(cancelled: bool, poll: F) -> bool

The intended usage would be:

if (!cancel_child || child_cancel_aware) {
    child_cancel_aware = with_cancel_state(cancel_child, || {
        poll(child_future, cx)
    })
}

That's all. Suddenly, all the goals we mentioned above is satisfied.

Analysis

First, let me explain a little about the 2 added API.

Let's check whether all the goals mentioned above is satisfied.

Implementation

I won't detail the implementation, because it is obvious.

On the opt-in side,

Darksonn commented 4 months ago

You mention io_uring and IOCP in your initial motivation, but your proposal does not actually address those use-cases?

Ultimately, I don't have the bandwidth to support a project like this in Tokio. You will probably have to prototype it elsewhere.

jxdabc commented 4 months ago

@Darksonn

Darksonn commented 4 months ago

I'm not sure how well the issue was understood in 2018, but it's understood well these days that for io_uring/IOCP, you must either use owned buffers (in which case you don't need any kind of AsyncDrop), or you need some way to say that the read/write future cannot be mem::forgotten, which requires language changes. Anything else is unsound.

I could prototype it by a PR prototype, if it would be helpful.

Sorry, it looks like I was not clear enough. Tokio is not going to accept a PR for this proposal. I will close this issue now.