What happens to an async task when it is aborted?
Asked Answered
A

1

10

Rust has async methods that can be tied to Abortable futures. The documentation says that, when aborted:

the future will complete immediately without making any further progress.

Will the variables owned by the task bound to the future be dropped? If those variables implement drop, will drop be called? If the future has spawned other futures, will all of them be aborted in a chain?

E.g.: In the following snippet, I don't see the destructor happening for the aborted task, but I don't know if it is not called or happens in a separate thread where the print is not shown.

use futures::executor::block_on;
use futures::future::{AbortHandle, Abortable};

struct S {
    i: i32,
}

impl Drop for S {
    fn drop(&mut self) {
        println!("dropping S");
    }
}

async fn f() -> i32 {
    let s = S { i: 42 };
    std::thread::sleep(std::time::Duration::from_secs(2));
    s.i
}

fn main() {
    println!("first test...");
    let (abort_handle, abort_registration) = AbortHandle::new_pair();
    let _ = Abortable::new(f(), abort_registration);
    abort_handle.abort();
    std::thread::sleep(std::time::Duration::from_secs(1));

    println!("second test...");
    let (_, abort_registration) = AbortHandle::new_pair();
    let task = Abortable::new(f(), abort_registration);
    block_on(task).unwrap();
    std::thread::sleep(std::time::Duration::from_secs(1));
}

playground

Adz answered 6/1, 2020 at 14:25 Comment(2)
Do not use blocking / thread sleeps in asynchronous code. Why does Future::select choose the future with a longer sleep period first?. What is the best approach to encapsulate blocking I/O in future-rs?.Matador
In the first test, you abort the future before it is ever scheduled for execution, so it never starts → it doesn't need to call the destructor because it never allocated s (playground)Hegelian
M
10

Yes, values that have been created will be dropped.

In your first example, the future returned by f is never started, so the S is never created. This means that it cannot be dropped.

In the second example, the value is dropped.

This is more obvious if you both run the future and abort it. Here, I spawn two concurrent futures:

  1. create an S and waits 200ms
  2. wait 100ms and abort future #1
use futures::future::{self, AbortHandle, Abortable};
use std::time::Duration;
use tokio::time;

struct S {
    i: i32,
}

impl S {
    fn new(i: i32) -> Self {
        println!("Creating S {}", i);
        S { i }
    }
}

impl Drop for S {
    fn drop(&mut self) {
        println!("Dropping S {}", self.i);
    }
}

#[tokio::main]
async fn main() {
    let create_s = async {
        let s = S::new(42);
        time::delay_for(Duration::from_millis(200)).await;
        println!("Creating {} done", s.i);
    };
    let (abort_handle, abort_registration) = AbortHandle::new_pair();
    let create_s = Abortable::new(create_s, abort_registration);

    let abort_s = async move {
        time::delay_for(Duration::from_millis(100)).await;
        abort_handle.abort();
    };

    let c = tokio::spawn(create_s);
    let a = tokio::spawn(abort_s);

    let (c, a) = future::join(c, a).await;

    println!("{:?}, {:?}", c, a);
}
Creating S 42
Dropping S 42
Ok(Err(Aborted)), Ok(())

Note that I've switched to Tokio to be able to use time::delay_for, as you should never use blocking operations in an async function.

See also:

If the future has spawned other futures, will all of them be aborted in a chain?

No, when you spawn a future, it is disconnected from where it was spawned.

See also:

Matador answered 6/1, 2020 at 14:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.