0

Right now, if you press Ctrl + C, the code will exit the loop that listens to Ctrl + C—but the process will only exit after the command has finished executing.

use std::{
    io::Write,
    process::{Command, Stdio},
    sync::{Arc, Mutex},
    thread,
    time::Duration,
};

use termion::{event::Key, input::TermRead, raw::IntoRawMode};

pub struct CmdRunner {}

impl CmdRunner {
    pub fn run<W: Write + Send + 'static>(&mut self, stdout_mutex: Arc<Mutex<Option<W>>>) {
        let mut command = Command::new("script");

        command.arg("-qec").arg("sleep 6").arg("/dev/null");

        command.stdout(Stdio::piped());
        command.stderr(Stdio::piped());

        let mut child = command.spawn().expect("failed to spawn command");
        let should_exit = Arc::new(Mutex::new(false));
        let should_exit_clone = Arc::clone(&should_exit);
        let mut stdin = termion::async_stdin().keys();

        let handle = std::thread::spawn(move || {
            loop {
                if *should_exit_clone.lock().unwrap() {
                    println!("Exited!");
                    break;
                }

                let input = stdin.next();

                if let Some(Ok(key)) = input {
                    match key {
                        Key::Ctrl('c') => {
                            *should_exit_clone.lock().unwrap() = true;
                        }
                        _ => {}
                    }
                }

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

        child.wait().expect("failed to wait for command");
        
        println!("The command finished executing!");

        *should_exit.lock().unwrap() = true;

        handle.join().unwrap();
    }
}

fn main() {
    let stdout = std::io::stdout().into_raw_mode().unwrap();
    let mut command = CmdRunner {};
    let stdout_mutex = Arc::new(Mutex::new(Some(stdout)));

    command.run(stdout_mutex);
}

It's because of this line: child.wait().expect("failed to wait for command") But I can't remove it, because the code has to wait for the command to finish if Ctrl + C isn't pressed.

How can I stop the command and exit the process gracefully when Ctrl + C is pressed?

Note: I removed the code that uses Arc<Mutex<W>>> to simplify the code.

alexchenco
  • 53,565
  • 76
  • 241
  • 413
  • 2
    I'm no rust guru, but I think your code doesn't listen for or pass on the SIGTERM signal to the command. A quick google indicates this should be possible; this answer (https://stackoverflow.com/a/56511444/535515) looks promising – Gus May 23 '23 at 13:38
  • @Gus So how do I pass the SIGTERM signal to the command in my code, causing it to stop? The answer you shared talks about SIGTERM, but it doesn't talk about how to stop a command. – alexchenco May 23 '23 at 13:46
  • 1
    Instead of `child.wait()` you need `child.try_wait()` in a loop, polling to see if it's exited on its own. And if you want to force kill it, call `child.kill()` on it. [doc for Child.kill](https://doc.rust-lang.org/std/process/struct.Child.html#method.kill). Heavy-handed, but should work. – Kevin Anderson May 23 '23 at 14:15
  • @KevinAnderson Thanks, it works: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=063f5e134f9ed369a4577131d06b7d59 Do you want to write a short answer? – alexchenco May 23 '23 at 15:44

1 Answers1

2

As per my response above, what you should do is instead of blocking on child.wait() you should use child.try_wait() which will tell you if it's done or not, then sleep in a loop. If you get the Ctrl-C signal, then you can child.kill() it.

In alex's Rust playground he keeps them a thread (which loops) and a main thread loop, but I recommend making this all in one loop, and ditch the thread entirely. If you're sleeping in the main thread anyways, what's the virtue of the extra thread? When you wake up from your try_wait() just check for Ctrl-C w and/or whatever mechanism you choose to use to receive either the keyboard command, or SIGTERM.

Kevin Anderson
  • 6,850
  • 4
  • 32
  • 54
  • 1
    Thanks for the advice. I just added the additional loop and didn't give it much though. I'll just have one loop. – alexchenco May 23 '23 at 15:57
  • 1
    Hey no worries. I'm sure everybody here has "just added one thing" and didn't zoom out slightly to see if the previous approach can be simplified. It's all good. Please click the "accept" button on my answer as well (if it's the one you want), as it helps. – Kevin Anderson May 23 '23 at 16:11