Infinitely running thread in rust

Rust infinite running thread

Infinitely Running Threads

I’ve had this weird idea for awhile, but how do you start an infinitely running thread in rust that will restart itself should it ever panic or crash? Is that even valid to do? Is it idiomatic?

Nope. Not even a little bit. Well, it’s probably okay up until that first panic! downstream.

The Closure

Closures in rust can be a few things, but the most important thing is that for a looping closure that needs to be called more than once it must be an FnMut as described here.

FnMut applies to closures that don’t move captured values out of their body, but that might mutate the captured values. These closures can be called more than once.

Since the point of this was to loop, aka, restart the “task” if it ever joins, it has to be fnMut.

Send + Sync

Because this is going to run in a thread, it must implement Send + Sync, as noted here.

The Sync marker trait indicates that it is safe for the type implementing Sync to be referenced from multiple threads.

'static will let the compiler know that there is not some data being moved into it that will be yeeted during that threads lifetime. Since I’m using move, it’s technically possible that the program terminates before the thread ever even launches (if you remove join, it could happen). You’ll zip right down fn main() and just die while the thread could be starting up still.

Compiler protections!

Arc

Because it’s a non-blocking task I needed to move it into another thread. Two, to be precise. One to keep the thing running, and then one to execute the task itself. I need Arc because I don’t want to move the threads ownership to the first loop i call. If i did, I would only ever get one execution, which defeats the point of this whole thing.

pub fn start<T>(&mut self, closure: T, sleep_for: Duration)
where
    T: FnMut() + Send + Sync + 'static,
{
    // Sharing is caring
    let closure = Arc::new(Mutex::new(closure));
    thread::spawn(move || {
        let closure_clone = Arc::clone(&closure);
        // start the outer loop which watches the inner task, restarting if needed
        thread::spawn(move || {
            // run the closure inside of here
             match closure_clone.lock() {
                // do the work here
             }
        })
    })
}

The Big Caveat

If the inner thread panics, you can sort of deal with it. And by deal with it, I mean tell Rust “I don’t care if it’s poisoned, give it to me anyway”.

 if let Err(_) = panic::catch_unwind(panic::AssertUnwindSafe(|| {
     
})) {
    // println!("Closure panicked. Restarting...");
    break; // Exit inner loop to restart the task thread.
}

In order for this to work, “unwind” aka the default must be set in the profle for rust in cargo.toml

[profile.release]
panic = "abort"

I’m not really sure how i feel about unwind, to be honest. I am not seasoned enough in rust yet to decide how to handle these, but generally I think relying on Result is easier and just making sure to capture as much as possible. The rest? Let it go into Sentry and get it in the next update.

In the case that panic is set to unwind, then we can catch it and recover the poisoned mutex. poison_error.into_inner().

However, this mutex is forever poisoned at that point. Every single execution is basically us telling Rust “I don’t care. Thanks”. We can attempt to set a new mutex, but I was not able to get it working any better.

A better approach

A better approach would be to likely let the panic fall through with a Result. However, that sort of defeats the purpose because the the main idea I had for this was to “run forever and recover yourself, I dont want to keep track of you” - which.. obviously is not right! By any accounts! How can you be sure the work is being done at all? Perhaps a result that sets a flag that execution was stopped, but then upstream in your main application you still need to keep track of it.

In Conclusion

A simple idea that turned into a waste of time. It’s just simply not a good idea, but having gone through the exercise showed me why it’s a terrible idea.

Keep track of your threads. It’s better that way.

If you want to see the code, I’ve published it here.