Skip to main content

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 checking the current process state fails, it still attempts
30/// best-effort termination. If termination fails, a panic is raised.
31#[derive(Debug)]
32pub struct TerminateOnDrop<O: OutputStream> {
33    pub(crate) process_handle: ProcessHandle<O>,
34    pub(crate) interrupt_timeout: Duration,
35    pub(crate) terminate_timeout: Duration,
36}
37
38impl<O: OutputStream> Deref for TerminateOnDrop<O> {
39    type Target = ProcessHandle<O>;
40
41    fn deref(&self) -> &Self::Target {
42        &self.process_handle
43    }
44}
45
46impl<O: OutputStream> DerefMut for TerminateOnDrop<O> {
47    fn deref_mut(&mut self) -> &mut Self::Target {
48        &mut self.process_handle
49    }
50}
51
52impl<O: OutputStream> Drop for TerminateOnDrop<O> {
53    fn drop(&mut self) {
54        async_drop::run_future(async {
55            match self.process_handle.is_running() {
56                crate::RunningState::Terminated(_) => {
57                    tracing::debug!(
58                        process = %self.process_handle.name,
59                        "Process already terminated"
60                    );
61                    return;
62                }
63                crate::RunningState::Running => {}
64                crate::RunningState::Uncertain(err) => {
65                    tracing::warn!(
66                        process = %self.process_handle.name,
67                        error = %err,
68                        "Could not determine process state during drop; attempting best-effort termination"
69                    );
70                }
71            }
72
73            tracing::debug!(process = %self.process_handle.name, "Terminating process");
74            match self
75                .process_handle
76                .terminate(self.interrupt_timeout, self.terminate_timeout)
77                .await
78            {
79                Ok(exit_status) => {
80                    tracing::debug!(
81                        process = %self.process_handle.name,
82                        ?exit_status,
83                        "Successfully terminated process"
84                    );
85                }
86                Err(err) => {
87                    panic!(
88                        "Failed to terminate process '{}': {}",
89                        self.process_handle.name, err
90                    );
91                }
92            }
93        });
94    }
95}