29

When I spawn a thread in Rust, I get a JoinHandle, which is good for... joining (a blocking operation), and not much else. How can I check if a child thread has exited (i.e., JoinHandle.join() would not block) from the parent thread? Bonus points if you know how to kill a child thread.

I imagine you could do this by creating a channel, sending something to the child, and catching errors, but that seems like needless complexity and overhead.

Doctor J
  • 5,974
  • 5
  • 44
  • 40

5 Answers5

20

As of Rust 1.7, there's no API in the standard library to check if a child thread has exited without blocking.

A portable workaround would be to use channels to send a message from the child to the parent to signal that the child is about to exit. Receiver has a non-blocking try_recv method. When try_recv does receive a message, you can then use join() on the JoinHandle to retrieve the thread's result.

There are also unstable platform-specific extension traits that let you obtain the raw thread handle. You'd then have to write platform-specific code to test whether the thread has exited or not.

If you think this feature should be in Rust's standard library, you can submit an RFC (be sure to read the README first!).

Bonus points if you know how to kill a child thread.

Threads in Rust are implemented using native OS threads. Even though the operating system might provide a way to kill a thread, it's a bad idea to do so, because the resources that the thread allocated will not be cleaned up until the process ends.

Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • If the thread never sends a message, but `try_recv` yields [`Disconnected`](https://doc.rust-lang.org/stable/std/sync/mpsc/enum.TryRecvError.html#variant.Disconnected), can we conclude that the thread has terminated anyway? – iago-lito Feb 10 '20 at 17:15
  • 2
    Not necessarily; a thread could drop its sender early (using `drop(sender);` or `let _ = sender;`, for example). – Francis Gagné Feb 11 '20 at 01:53
  • Very true. Now, if those are the only cases of false positive, then I guess the thread could receive `_sender` as an argument and never use it. This way, `_sender` would act as a lifeline.. unless the compiler decides to immediately deallocate it? – iago-lito Feb 11 '20 at 12:42
  • 1
    No, `_sender` is a valid variable name (unlike `_`), so the destructor will run when that variable goes out of scope, not earlier, unless you move out of that variable yourself. No level of optimization will change that. – Francis Gagné Feb 12 '20 at 01:26
  • 1
    Great! So I can check whether a child thread has exited without blocking and with only `std`. The trick is to `let (rx, tx) = mpsc::channel();`, move the `tx` side to the child thread and regularly check `rx.try_recv()` until it yields [`Err(Disconnected)`](https://doc.rust-lang.org/std/sync/mpsc/enum.TryRecvError.html#variant.Disconnected). On its side, the thread needs to receive `_tx` and promise not to use it at all. The only downside I can think of is that I cannot enforce that the child thread won't drop `_tx` early if I it has been written by somebody else :\ – iago-lito Feb 12 '20 at 08:48
14

The short answer is not possible yet. But this is not the point that should really be addressed.

Bonus points if you know how to kill a child thread.

NEVER

Even in languages that do support killing threads (see Java here), it is recommended not to.

A thread's execution is generally coded with explicit points of interactions, and there are often implicit assumptions that no other interruption will occur.

The most egregious example is of course resources: the naive "kill" method would be to stop executing the thread; this would mean not releasing any resource. You may think about memory, it's the least of your worries. Imagine, instead, all the Mutex that are not unlocked and will create deadlocks later...

The other option would be to inject a panic in the thread, which would cause unwinding. However, you cannot just start unwinding at any point! The program would have to define safe points at which injecting a panic would be guaranteed to be safe (injecting it at any other point means potentially corrupting shared objects); how to define such safe points and inject the panic there is an open research problem in native languages, especially those executed on systems W^X (where memory pages are either Writable or Executable but never both).

In summary, there is no known way to safely (both memory-wise and functionality-wise) kill a thread.

Community
  • 1
  • 1
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
14

It's possible, friends. Use refcounters which Rust will drop on end or panic. 100% safe. Example:

use std::time::Duration;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;

fn main() {
    // Play with this flag
    let fatal_flag = true;
    let do_stop = true;

    let working = Arc::new(AtomicBool::new(true));
    let control = Arc::downgrade(&working);

    thread::spawn(move || {
        while (*working).load(Ordering::Relaxed) {
            if fatal_flag {
                panic!("Oh, my God!");
            } else {
                thread::sleep(Duration::from_millis(20));
                println!("I'm alive!");
            }
        }
    });

    thread::sleep(Duration::from_millis(50));

    // To stop thread
    if do_stop {
        match control.upgrade() {
            Some(working) => (*working).store(false, Ordering::Relaxed),
            None => println!("Sorry, but thread died already."),
        }
    }

    thread::sleep(Duration::from_millis(50));

    // To check it's alive / died
    match control.upgrade() {
        Some(_) => println!("Thread alive!"),
        None => println!("Thread ends!"),
    }
}

Gist: https://gist.github.com/DenisKolodin/edea80f2f5becb86f718c330219178e2

At playground: https://play.rust-lang.org/?gist=9a0cf161ba0bbffe3824b9db4308e1fb&version=stable&backtrace=0

UPD: I've created thread-control crate which implements this approach: https://github.com/DenisKolodin/thread-control

DenisKolodin
  • 13,501
  • 3
  • 62
  • 65
5

As of rust 1.61.0, there is an is_finished method.

https://doc.rust-lang.org/stable/std/thread/struct.JoinHandle.html#method.is_finished

Blue7
  • 1,750
  • 4
  • 31
  • 55
3

I think Arc can be used to solve this problem If the thread exits, the reference counter is reduced by one

kingmeng
  • 43
  • 6