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}