3

I am trying to run a thread in background and then change an AtomicBool to ask the thread to stop. To ensure the thread stopped properly, I want to call join method of the JoinHandle.

Instead of calling join, if I just wait (thread::sleep_ms) for sometime after setting the AtomicBool, the thread properly closes. But to ensure this I want to use join. Or is there any better method to ensure the thread closed properly?

use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::thread::JoinHandle;

struct MyInner { s: String }

struct My {
    inner: Arc<Mutex<MyInner>>,
    close_thread: Arc<AtomicBool>,
    my_thread: JoinHandle<()>
}

impl MyInner {
    fn new(s: String) -> MyInner {
        MyInner {
            s: s
        }
    }
}

impl My {
    fn new(s: String) -> My {
        My {
            inner: Arc::new(Mutex::new(MyInner::new(s))),
            close_thread: Arc::new(AtomicBool::new(false)),
            my_thread: thread::spawn(move || {})
        }
    }

    fn stop_thread(&self) {
        self.close_thread.swap(true, Ordering::Relaxed);
        // ERROR!
        self.my_thread.join().expect("Couldn't join my_thread on the main thread");
    }

    fn start(&mut self) {
        let local_self = self.inner.clone();
        let close_thread = self.close_thread.clone();
        self.my_thread  = thread::spawn(move || {
            loop {
                if close_thread.load(Ordering::Relaxed) {
                    println!("Closing thread!");
                    return;
                }
                let gaurd = local_self.lock().unwrap();
                println!("Local self has value: {}", (*gaurd).s);
                std::mem::drop(gaurd);
                thread::sleep_ms(1000);
            }
        });
    }
}

fn main() {
    let mut m = My::new("blo".to_owned());
    m.start();
    thread::sleep_ms(2000);
    m.stop_thread();
    println!("Complete!");
}

For the above code, I get the error:

error[E0507]: cannot move out of `self.my_thread` which is behind a shared reference
  --> src/main.rs:35:9
   |
35 |         self.my_thread.join().expect("Couldn't join my_thread on the main thread");
   |         ^^^^^^^^^^^^^^ move occurs because `self.my_thread` has type `std::thread::JoinHandle<()>`, which does not implement the `Copy` trait

I also tried using Arc on the JoinHandle but even after Arc::clone I get the same error.

Boiethios
  • 25,767
  • 8
  • 91
  • 135
lucifer
  • 139
  • 12
  • 8
    Closing a `JoinHandle` moves it out, to prevent closing it twice. Nothing would prevent a user from calling `stop_thread` twice. You either need to make `stop_thread(self)` or to store eg. a `Option`. – mcarton Aug 27 '19 at 08:10
  • 5
    `Option>` is the way to go. Use it like: `join_handle.take().map(JoinHandle::join)` – Boiethios Aug 27 '19 at 08:28
  • 1
    @mcarton why didn't you post that as an answer? That comment contains exactly everything that needs to be in an answer. – SOFe Aug 27 '19 at 08:50
  • @SOFe That's a duplicate. – Boiethios Aug 27 '19 at 08:52
  • 2
    @SOFe I'm pretty since has already been asked, but didn't have time to search for a duplicate or make a proper answer. – mcarton Aug 27 '19 at 08:54
  • @French Boithios Thank you, I needed exactly that. It works and `take` leaves the join_handle empty. So, that's a bonus. – lucifer Aug 27 '19 at 09:41
  • @mcarton I could simply check the a flag before calling `my_thread.join`. What does `stop_thread(self)` achieve? Sorry, I am new to rust and all these ownership errors feel way too many restrictions. Shouldn't this have been a warning instead? – lucifer Aug 27 '19 at 09:44
  • 1
    "Shouldn't this have been a warning instead?" No. One of Rust's strong point is that it tries to do as much as possible at compile time. In this case it's easy to prevent this kind of error at compile time using Rust's type system. In your case I would either [use Option](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d8f11ea2fbffd11c9d8892f0ae5cd7db) or [consume `self`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=14d21d96bf1fce52a3beb50bb5cc2dda) depending on the intended use-case. – mcarton Aug 27 '19 at 09:55
  • 1
    _"I could simply check a flag"_, yes but you didn't, did you? `Option` is the way that Rust uses to make sure that you do check the flag when needed. – Jmb Aug 27 '19 at 11:53
  • @jmb it is a test code. I would never put it in production. Checking a simple flag is not that difficult. Rust has more spoonfeeding than I would like. Anyway, using Option solved this issue. And to people marking this as a duplicate, really? I could never have figured out the solution by reading any of the linked issues. – lucifer Aug 28 '19 at 02:07

0 Answers0