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}