tokio_process_tools/terminate_on_drop.rs
1use crate::process_handle::ProcessHandle;
2use crate::{OutputStream, async_drop};
3use std::ops::{Deref, DerefMut};
4use std::time::Duration;
5
6/// A wrapper that automatically terminates a process when dropped.
7///
8/// # Safety Requirements
9///
10/// **WARNING**: This type requires a multithreaded tokio runtime to function correctly!
11///
12/// # Usage Guidelines
13///
14/// This type should only be used when:
15/// - Your code is running in a multithreaded tokio runtime.
16/// - Automatic process cleanup on drop is absolutely necessary.
17///
18/// # Recommended Alternatives
19///
20/// Instead of relying on automatic termination, prefer these safer approaches:
21/// 1. Manual process termination using [`ProcessHandle::terminate`]
22/// 2. Awaiting process completion using [`ProcessHandle::wait_for_completion`]
23/// 3. Awaiting process completion or performing an explicit termination using
24/// [`ProcessHandle::wait_for_completion_or_terminate`]
25///
26/// # Implementation Details
27///
28/// The drop implementation tries to terminate the process if it was neither awaited nor
29/// terminated before being dropped. If termination fails, a panic is raised.
30#[derive(Debug)]
31pub struct TerminateOnDrop<O: OutputStream> {
32 pub(crate) process_handle: ProcessHandle<O>,
33 pub(crate) interrupt_timeout: Duration,
34 pub(crate) terminate_timeout: Duration,
35}
36
37impl<O: OutputStream> Deref for TerminateOnDrop<O> {
38 type Target = ProcessHandle<O>;
39
40 fn deref(&self) -> &Self::Target {
41 &self.process_handle
42 }
43}
44
45impl<O: OutputStream> DerefMut for TerminateOnDrop<O> {
46 fn deref_mut(&mut self) -> &mut Self::Target {
47 &mut self.process_handle
48 }
49}
50
51impl<O: OutputStream> Drop for TerminateOnDrop<O> {
52 fn drop(&mut self) {
53 async_drop::run_future(async {
54 if !self.process_handle.is_running().as_bool() {
55 tracing::debug!(
56 process = %self.process_handle.name,
57 "Process already terminated"
58 );
59 return;
60 }
61
62 tracing::debug!(process = %self.process_handle.name, "Terminating process");
63 match self
64 .process_handle
65 .terminate(self.interrupt_timeout, self.terminate_timeout)
66 .await
67 {
68 Ok(exit_status) => {
69 tracing::debug!(
70 process = %self.process_handle.name,
71 ?exit_status,
72 "Successfully terminated process"
73 )
74 }
75 Err(err) => {
76 panic!(
77 "Failed to terminate process '{}': {}",
78 self.process_handle.name, err
79 );
80 }
81 };
82 });
83 }
84}