tokio_process_tools/terminate_on_drop.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
use crate::process_handle::ProcessHandle;
use std::time::Duration;
#[derive(Debug)]
pub struct TerminateOnDrop {
pub(crate) process_handle: ProcessHandle,
pub(crate) graceful_termination_timeout: Option<Duration>,
pub(crate) forceful_termination_timeout: Option<Duration>,
}
impl Drop for TerminateOnDrop {
fn drop(&mut self) {
// 1. We are in a Drop implementation which is synchronous - it can't be async.
// But we need to execute an async operation (the `terminate` call).
//
// 2. `Block_on` is needed because it takes an async operation and runs it to completion
// synchronously - it's how we can execute our async terminate call within the synchronous
// drop.
//
// 3. However, block_on by itself isn't safe to call from within an async context
// (which we are in since we're inside the Tokio runtime).
// This is because it could lead to deadlocks - imagine if the current thread is needed to
// process some task that our blocked async operation is waiting on.
//
// 4. This is where block_in_place comes in - it tells Tokio:
// "hey, I'm about to block this thread, please make sure other threads are available to
// still process tasks". It essentially moves the blocking work to a dedicated thread pool
// so that the async runtime can continue functioning.
//
// 5. Note that `block_in_place` requires a multithreaded tokio runtime to be active!
// So use `#[tokio::test(flavor = "multi_thread")]` in tokio-enabled tests.
//
// 6. Also note that `block_in_place` enforces that the given closure runs to completion,
// even when the async executor is terminated - this might be because our program ended
// or because we crashed due to a panic.
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
if !self.process_handle.is_running().as_bool() {
tracing::debug!(
process = %self.process_handle.name,
"Process already terminated"
);
return;
}
tracing::debug!(process = %self.process_handle.name, "Terminating process");
match self
.process_handle
.terminate(
self.graceful_termination_timeout,
self.forceful_termination_timeout,
)
.await
{
Ok(exit_status) => {
tracing::debug!(
process = %self.process_handle.name,
?exit_status,
"Successfully terminated process"
)
}
Err(err) => {
panic!(
"Failed to terminate process '{}': {}",
self.process_handle.name, err
);
}
};
});
});
}
}