Using Arc<()> to terminate a thread

I think it's a reasonable solution, but it would be a bit more idiomatic/semantically correct to leave the only copy of the strong Arc in the parent thread, and only leave a weak reference in the child. Then you could do

while keepalive_recv.upgrade().is_some() {
    ...
}

which IMO signals the intention of single owneship better.

I would also recommend newtyping both halves, so that you can't accidentally (or intentionally) clone either half, and there is always truly a single owner only. I'd do that at least for the receiving end so that the child can't keep itself alive.

Actually, it's pretty easy to incorporate this into a single newtype and a function hiding the implementation details of the keepalive-checking loop: Playground

use core::time::Duration;
use core::ops::Deref;
use std::sync::Arc;
use std::thread::{self, JoinHandle};

#[derive(Debug)]
pub struct KeepaliveHandle {
    keepalive: Arc<()>,
    join: JoinHandle<()>,
}

impl Deref for KeepaliveHandle {
    type Target = JoinHandle<()>;
    
    fn deref(&self) -> &Self::Target {
        &self.join
    }
}

pub fn spawn_keepalive<F: FnMut() + Send + 'static>(mut worker: F) -> KeepaliveHandle {
    let keepalive = Arc::default();
    let recv = Arc::downgrade(&keepalive);
    
    let join = thread::spawn(move || {
        while recv.upgrade().is_some() {
            worker();
        }
    });
    
    KeepaliveHandle { keepalive, join }
}

fn main() {
    let mut counter = 0;
    let handle = spawn_keepalive(move || {
        println!("working: {}", counter);
        counter += 1;
        thread::sleep(Duration::from_secs(1));
    });
    
    println!("Spawned thread");
    thread::sleep(Duration::from_secs(5));
    drop(handle);
    println!("Finished");
}
8 Likes