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<Stdout: OutputStream, Stderr: OutputStream = Stdout> {
33    pub(crate) process_handle: ProcessHandle<Stdout, Stderr>,
34    pub(crate) interrupt_timeout: Duration,
35    pub(crate) terminate_timeout: Duration,
36}
37
38impl<Stdout, Stderr> Deref for TerminateOnDrop<Stdout, Stderr>
39where
40    Stdout: OutputStream,
41    Stderr: OutputStream,
42{
43    type Target = ProcessHandle<Stdout, Stderr>;
44
45    fn deref(&self) -> &Self::Target {
46        &self.process_handle
47    }
48}
49
50impl<Stdout, Stderr> DerefMut for TerminateOnDrop<Stdout, Stderr>
51where
52    Stdout: OutputStream,
53    Stderr: OutputStream,
54{
55    fn deref_mut(&mut self) -> &mut Self::Target {
56        &mut self.process_handle
57    }
58}
59
60impl<Stdout, Stderr> Drop for TerminateOnDrop<Stdout, Stderr>
61where
62    Stdout: OutputStream,
63    Stderr: OutputStream,
64{
65    fn drop(&mut self) {
66        async_drop::run_future(async {
67            match self.process_handle.is_running() {
68                crate::RunningState::Terminated(_) => {
69                    tracing::debug!(
70                        process = %self.process_handle.name,
71                        "Process already terminated"
72                    );
73                    // The child has exited; close the lifecycle so the inner ProcessHandle's
74                    // drop guards do not panic when this wrapper drops out of scope.
75                    self.process_handle.must_not_be_terminated();
76                    return;
77                }
78                crate::RunningState::Running => {}
79                crate::RunningState::Uncertain(err) => {
80                    tracing::warn!(
81                        process = %self.process_handle.name,
82                        error = %err,
83                        "Could not determine process state during drop; attempting best-effort termination"
84                    );
85                }
86            }
87
88            tracing::debug!(process = %self.process_handle.name, "Terminating process");
89            match self
90                .process_handle
91                .terminate(self.interrupt_timeout, self.terminate_timeout)
92                .await
93            {
94                Ok(exit_status) => {
95                    tracing::debug!(
96                        process = %self.process_handle.name,
97                        ?exit_status,
98                        "Successfully terminated process"
99                    );
100                }
101                Err(err) => {
102                    tracing::error!(
103                        process = %self.process_handle.name,
104                        error = %err,
105                        "Failed to terminate process during drop"
106                    );
107                }
108            }
109        });
110    }
111}