How to make sure that child process terminates when panicking

Hello,

I'm making a program that has many long running tasks in the background and when it panics those processes keep running which I don't want. I run the Command::new("something").spawn().wait() in a separate thread (which I don't join) because I need to wrap it in an infinite loop in case the command terminates unexpectedly.

I tried having a global Arc<Mutex<Vec<Child>>> but that didn't work because after having moved the value inside I can't wait on it anymore, because (I think) wait takes a &mut self.

I have tried wrapping the Child into a struct and implementing Drop for it and that doesn't really work, here are the things I tried.

Any idea?

use std::process::{Command,Child};

struct ChildGuard(Child);

impl Drop for ChildGuard {
    fn drop(&mut self) {
        // You can check std::thread::panicking() here
        match self.0.kill() {
            Err(e) => println!("Could not kill child process: {}", e),
            Ok(_) => println!("Successfully killed child process"),
        }
    }
}

/* Content of hang.sh

#! /bin/sh

echo "Starting hang"
sleep 6
echo "Ending hang"

*/

fn main() {
    //main1();
    main2();
    //main3();
}

// Child process keep running in background after panic
fn main1() {
    let t = std::thread::spawn(move || {
        let child = Command::new("./hang.sh").spawn().unwrap();
	let mut _guard = ChildGuard(child);
    	_guard.0.wait();
    });

    std::thread::sleep(std::time::Duration::from_secs(4));
    panic!("Main thread panicking");
}

// Child process keep running in background after panic
fn main2() {
    let t = std::thread::spawn(move || {
        let child = Command::new("./hang.sh").spawn().unwrap();
	let mut _guard = ChildGuard(child);
    	std::thread::sleep(std::time::Duration::from_secs(2));
        panic!("Sub thread panicking");
    });
    t.join();
}

// Child process terminates
fn main3() {
    let t = std::thread::spawn(move || {
        let child = Command::new("./hang.sh").spawn().unwrap();
	let mut _guard = ChildGuard(child);
    	std::thread::sleep(std::time::Duration::from_secs(2));
        panic!("Sub thread panicking");
    });
    std::thread::sleep(std::time::Duration::from_secs(4));
    panic!("Main thread panicking");
}
1 Like

You can't rely on unwinding in applications compiled with panic=abort. However, you can set a panic hook to run code on panic:

Unfortunately, there's only one global hook, so be careful not to mix it with other functions/libraries that want to use the hook too.

1 Like

You could use an Arc<Mutex<Vec<Child>>>, but make a tweak to your thread. Instead of blocking on wait, it could use a (sleep, try_wait) loop. It'd lock the mutex during the try_wait.

Nothing in the parent process can absolutely guarantee this. Your panic handler may never run - your program could be interrupted by an oomkill (on Linux), for example, or the administrator may force-terminate it (SIGKILL or its Windows equivalent), preventing your cleanup code from running.

Doing this right requires cooperation either from the child process, or from a process management tool. There are a few ways to do that depending on what OS or OSes you want to target and just how reliable you want it to be. What the child process is doing may also make a difference. Can you say more about your use case?

1 Like

That is a good idea. Not really elegant but what can you do :confused:

I would also have to combine it with a panic_handler that would kill all remaining processes in case of panic.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.