-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Kill subprocesses correctly on each platform on Ctrl-C
- Loading branch information
1 parent
1a21963
commit a8b7564
Showing
7 changed files
with
161 additions
and
19 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#[cfg(windows)] | ||
mod windows; | ||
|
||
#[cfg(windows)] | ||
pub use windows::run; | ||
|
||
#[cfg(unix)] | ||
mod unix; | ||
|
||
#[cfg(unix)] | ||
pub use unix::run; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
//! On Unix, we use tokio to spawn processes so that we can listen for signals | ||
//! and wait for process completion at the same time. | ||
use std::io::{Error, ErrorKind}; | ||
use std::path::Path; | ||
use std::thread; | ||
|
||
use signal_hook::consts::signal::{SIGABRT, SIGINT, SIGQUIT, SIGTERM}; | ||
use signal_hook::iterator::Signals; | ||
use tokio::process::Command; | ||
use tokio::sync::oneshot; | ||
|
||
pub fn run(exe_path: &Path, args: Vec<String>) -> Result<i32, Error> { | ||
let (kill_tx, kill_rx) = oneshot::channel(); | ||
|
||
// Spawn a thread dedicated to listening for signals and relaying them to | ||
// our async runtime. | ||
let (signal_thread, signal_handle) = { | ||
let mut signals = Signals::new(&[SIGABRT, SIGINT, SIGQUIT, SIGTERM]).unwrap(); | ||
let signal_handle = signals.handle(); | ||
|
||
let thread = thread::spawn(move || { | ||
if let Some(signal) = signals.into_iter().next() { | ||
kill_tx.send(signal).ok(); | ||
} | ||
}); | ||
|
||
(thread, signal_handle) | ||
}; | ||
|
||
let runtime = tokio::runtime::Builder::new_current_thread() | ||
.enable_io() | ||
.build() | ||
.map_err(|_| Error::new(ErrorKind::Other, "could not create tokio runtime"))?; | ||
|
||
let _guard = runtime.enter(); | ||
|
||
let mut child = Command::new(exe_path).args(args).spawn().map_err(|_| { | ||
Error::new( | ||
ErrorKind::Other, | ||
format!("could not spawn {}", exe_path.display()), | ||
) | ||
})?; | ||
|
||
let code = runtime.block_on(async move { | ||
tokio::select! { | ||
// If the child exits cleanly, we can return its exit code directly. | ||
// I wish everything were this tidy. | ||
status = child.wait() => { | ||
let code = status.ok().and_then(|s| s.code()).unwrap_or(1); | ||
signal_handle.close(); | ||
signal_thread.join().unwrap(); | ||
|
||
code | ||
} | ||
|
||
// If we received a signal while the process was running, murder it | ||
// and exit immediately with the correct error code. | ||
code = kill_rx => { | ||
child.kill().await.ok(); | ||
signal_handle.close(); | ||
signal_thread.join().unwrap(); | ||
std::process::exit(128 + code.unwrap_or(0)); | ||
} | ||
} | ||
}); | ||
|
||
Ok(code) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
//! On Windows, we use command_group to spawn processes in a job group that will | ||
//! be automatically cleaned up when this process exits. | ||
use std::io::{Error, ErrorKind}; | ||
use std::path::Path; | ||
use std::process::Command; | ||
|
||
use command_group::CommandGroup; | ||
|
||
pub fn run(exe_path: &Path, args: Vec<String>) -> Result<i32, Error> { | ||
// On Windows, using a job group here will cause the subprocess to terminate | ||
// automatically when Aftman is terminated. | ||
let mut child = Command::new(exe_path) | ||
.args(args) | ||
.group_spawn() | ||
.map_err(|_| { | ||
Error::new( | ||
ErrorKind::Other, | ||
format!("Could not spawn {}", exe_path.display()), | ||
) | ||
})?; | ||
let status = child.wait()?; | ||
Ok(status.code().unwrap_or(1)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters