The control signal set off by Ctrl + C is indeed caught by a cmd.exe
instance launched when running mvn.cmd
.
Process tree when PowerShell is launched with its own GUI:
conhost.exe
└─ powershell.exe
└─ rust_program.exe
└─ cmd.exe /C mvn.cmd [...]
└─ java.exe [...]
↑
Control signal is propagated down from conhost.exe and caught by cmd.exe
One way to avoid this would be to launch java.exe
with the right arguments directly, but it's very hard to maintain. But don't worry! There is a better way:
The native Child::wait
and Child::kill
functions in the standard library both take &mut
references, which means they can't be used at the same time. The shared_child
crate was created just for this purpose!
The ctrlc
crate can be used to catch the signal set off by Ctrl + C. Its signal handler can only take 'static
and Send
references (basically meaning it can't take references to a value made in a function), but we can use an Arc
to share the SharedChild
across the Send + 'static
boundary.
use std::{
process::{Command, ExitStatus},
sync::Arc,
};
use shared_child::SharedChild;
fn run_maven(main_class: &str) -> std::io::Result<ExitStatus> {
let mut command = Command::new("mvn.cmd")
.args(&[
"exec:java",
&format!("-Dexec.mainClass={main_class}"),
"-Dexec.cleanupDaemonThreads=false",
"--quiet"
]);
// Spawn the child process and wrap it in an Arc
let child = Arc::new(SharedChild::spawn(&mut command)?);
// Clone the Arc so it can be moved into the Ctrl+C handler's closure
let child_clone = Arc::clone(&child);
// Set the Ctrl+C handler to kill the child process when triggered
ctrlc::set_handler(move || {
eprintln!("\nCtrl-C pressed! Killing Maven");
let _ = child_clone.kill();
})
.expect("Failed to set Ctrl-C handler");
// Wait for the child to finish and return exit status
child.wait()
}