2

I have the following code:

extern crate futures;
extern crate futures_cpupool;
extern crate tokio_timer;

use std::time::Duration;

use futures::Future;
use futures_cpupool::CpuPool;
use tokio_timer::Timer;

fn work(foo: Foo) {
    std::thread::sleep(std::time::Duration::from_secs(10));
}

#[derive(Debug)]
struct Foo { }

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

fn main() {
    let pool = CpuPool::new_num_cpus();

    let foo = Foo { };

    let work_future = pool.spawn_fn(|| {
        let work = work(foo);

        let res: Result<(), ()> = Ok(work);
        res
    });

    println!("Created the future");

    let timer = Timer::default();
    let timeout = timer.sleep(Duration::from_millis(750))
        .then(|_| Err(()));

    let select = timeout.select(work_future).map(|(win, _)| win);

    match select.wait() {
        Ok(()) => { },
        Err(_) => { },
    }
}

It seems this code doesn't execute Foo::drop - no message is printed.

I expected foo to be dropped as soon as timeout future resolves in select, as it's a part of environment of a closure, passed to dropped future.

How to make it execute Foo::drop?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Michael Pankov
  • 3,581
  • 2
  • 23
  • 31
  • 1
    It looks to me like the `foo` is being used in a thread that remains past the program termination. Cf. https://users.rust-lang.org/t/stopping-a-thread/6328. I wonder if http://stackoverflow.com/questions/26199926/how-to-terminate-or-suspend-a-rust-thread-from-another-thread is sufficient to answer your question? – ArtemGr Apr 18 '17 at 21:47

1 Answers1

3

The documentation for CpuPool states:

The worker threads associated with a thread pool are kept alive so long as there is an open handle to the CpuPool or there is work running on them. Once all work has been drained and all references have gone away the worker threads will be shut down.

Additionally, you transfer ownership of foo from main to the closure, which then transfers it to work. work will drop foo at the end of the block. However, work is also performing a blocking sleep operation. This sleep counts as work running on the thread.

The sleep is still going when the main thread exits, which immediately tears down the program, and all the threads, without any time to clean up.

As pointed out in How to terminate or suspend a Rust thread from another thread? (and other questions in other languages), there's no safe way to terminate a thread.

I expected foo to be dropped as soon as timeout future resolves in select, as it's a part of environment of a closure, passed to dropped future.

The future doesn't actually "have" the closure or foo. All it has is a handle to the thread:

pub struct CpuFuture<T, E> {
    inner: Receiver<thread::Result<Result<T, E>>>,
    keep_running_flag: Arc<AtomicBool>,
}

Strangely, the docs say:

If the returned future is dropped then this CpuPool will attempt to cancel the computation, if possible. That is, if the computation is in the middle of working, it will be interrupted when possible.

However, I don't see any implementation for Drop for CpuFuture, so I don't see how it could be possible (or safe). Instead of Drop, the threadpool itself runs a Future. When that future is polled, it checks to see if the receiver has been dropped. This behavior is provided by the oneshot::Receiver. However, this has nothing to do with threads, which are outside the view of the future.

Community
  • 1
  • 1
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 3
    - "*However, I don't see any implementation for Drop for CpuFuture*" - The `Drop` is in the `Receiver` (cf. https://github.com/alexcrichton/futures-rs/blob/master/src/sync/oneshot.rs). – ArtemGr Apr 18 '17 at 23:45
  • So, there's really no way to call destructors automatically with futures? – Michael Pankov Apr 19 '17 at 08:02
  • @MichaelPankov I think that's the wrong angle to look at it from. In Rust, destructors aren't guaranteed to run, but when they are it's always automatically. Futures are no different - the destructor will be called when the variable naturally goes out of scope. Your problem centers around the fact that you've started a thread that is still performing work when the main thread exits. The problem is completely orthogonal to futures. – Shepmaster Apr 19 '17 at 12:35
  • @Shepmaster somehow I got the impression (seems wrong now) that the entire purpose of future-based cpupool is to be able to abort the work it started via future, if the future isn't needed anymore. I then though aborting means dropping everything that was needed for that work. I actually thought that the system is similar to userspace scheduler - that it has access to how long a thread is executing. Destruction of pool would mean stopping all threads. Does anything like that exist for Rust? – Michael Pankov Apr 20 '17 at 13:54
  • @MichaelPankov AFAIK, futures *are* what you want, you are just trying to mix two competing abstractions (see [What Color is Your Function?](http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/)). Futures have *no ability* to stop a `thread::sleep` (and neither does anything else). The solution is to build everything with an eye to futures. This is why there's a `timer.sleep` — that builds something that "sleeps" for a while but is futures-aware. – Shepmaster Apr 20 '17 at 14:35
  • @MichaelPankov A "userspace scheduler" sounds like *green threads*, which the Rust standard library used to have but removed. It's even mentioned in [the blog post introducing futures](https://aturon.github.io/blog/2016/08/11/futures/). To reiterate: there is **no safe way** to abort an operating system thread; there's simply not enough control. Userspace "threads" need to communicate with the userspace "scheduler", and one way of doing that is via a future. If you don't communicate, there's nothing to be done. – Shepmaster Apr 20 '17 at 14:38
  • @Shepmaster thing is, `thread::sleep` is there as a stub of some "uninterruptable" work. I understand that if my work is cyclic, I can at least check some shared flag and stop if it's set. But what about interrupting a generic computation? – Michael Pankov Apr 21 '17 at 16:15