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