rustenium_core/
process.rs1use std::process::Stdio;
2
3use regex::Regex;
4use tokio::io::{AsyncBufReadExt, BufReader};
5use tokio::process::{Child, Command};
6use tokio::time::{timeout, Duration};
7
8#[derive(Debug)]
9pub struct Process {
10 child: Option<Child>,
11}
12
13impl Process {
14 pub fn create<S, I>(exe_path: S, args: I) -> Process
15 where
16 S: AsRef<str>,
17 I: IntoIterator<Item = String>,
18 {
19 let exe = exe_path.as_ref();
20 let args_vec: Vec<String> = args.into_iter().collect();
21
22 tracing::info!(
24 "Starting process with executable: '{}' and arguments: {:?}",
25 exe,
26 args_vec
27 );
28 tracing::info!(
29 "Full command: {} {}",
30 exe,
31 args_vec.join(" ")
32 );
33
34 let mut child = Command::new(exe)
35 .args(args_vec)
36 .stdout(Stdio::piped())
37 .stderr(Stdio::piped())
38 .kill_on_drop(true)
39 .spawn()
40 .expect("Failed to start process");
41
42 if let Some(stdout) = child.stdout.take() {
44 let exe_name = exe.to_string();
45 tokio::spawn(async move {
46 let mut reader = BufReader::new(stdout);
47 let mut line = String::new();
48 loop {
49 line.clear();
50 match reader.read_line(&mut line).await {
51 Ok(0) => break, Ok(_) => {
53 tracing::debug!("[{} stdout] {}", exe_name, line.trim());
54 }
55 Err(e) => {
56 tracing::error!("[{} stdout] Error reading: {}", exe_name, e);
57 break;
58 }
59 }
60 }
61 });
62 }
63
64 if let Some(stderr) = child.stderr.take() {
66 let exe_name = exe.to_string();
67 tokio::spawn(async move {
68 let mut reader = BufReader::new(stderr);
69 let mut line = String::new();
70 loop {
71 line.clear();
72 match reader.read_line(&mut line).await {
73 Ok(0) => break, Ok(_) => {
75 tracing::debug!("[{} stderr] {}", exe_name, line.trim());
76 }
77 Err(e) => {
78 tracing::error!("[{} stderr] Error reading: {}", exe_name, e);
79 break;
80 }
81 }
82 }
83 });
84 }
85
86 let child = Some(child);
87
88 return Self { child };
89 }
90
91 #[deprecated]
92 pub async fn wait_for_pattern(&mut self, pattern: &str, timeout_secs: Option<u64>) -> String {
93 let timeout_secs = timeout_secs.unwrap_or(20);
94 let regex = Regex::new(pattern).expect("Invalid regex pattern");
95 let child = self.child.as_mut().unwrap();
96
97 let stdout = child.stdout.as_mut().expect("Failed to access stdout");
98 let stderr = child.stderr.as_mut().expect("Failed to access stderr");
99
100 let mut stdout_lines = BufReader::new(stdout).lines();
101 let mut stderr_lines = BufReader::new(stderr).lines();
102
103 let check_line = |_label: &str, line: Result<Option<String>, _>| -> Option<String> {
104 if let Ok(Some(line)) = line {
105 if let Some(captures) = regex.captures(&line) {
106 if let Some(url) = captures.get(1) {
107 return Some(url.as_str().into());
108 }
109 }
110 }
111 None
112 };
113
114 let timeout_duration = Duration::from_secs(timeout_secs);
115
116 let timeout_result = timeout(timeout_duration, async {
117 loop {
118 tokio::select! {
119 stdout_line = stdout_lines.next_line() => {
120 if let Some(line) = check_line("stdout", stdout_line) {
121 return Some(line);
122 }
123 },
124 stderr_line = stderr_lines.next_line() => {
125 if let Some(line) = check_line("stderr", stderr_line) {
126 return Some(line);
127 }
128 }
129 }
130 }
131 })
132 .await;
133
134 match timeout_result {
135 Ok(Some(matched)) => matched,
136 Ok(None) => panic!("Found a pattern but None"),
137 Err(_) => panic!("Timeout reached without finding pattern"),
138 }
139 }
140}
141
142impl Process {
143 pub fn kill(&mut self) -> Result<(), crate::error::ProcessKillError> {
144 if let Some(mut child) = self.child.take() {
145 if let Some(pid) = child.id() {
146 let pid_str = pid.to_string();
147 #[cfg(unix)]
148 {
149 let _ = std::process::Command::new("pkill")
150 .args(["-9", "-P", &pid_str])
151 .output();
152 }
153 #[cfg(windows)]
154 {
155 let _ = std::process::Command::new("taskkill")
156 .args(["/F", "/T", "/PID", &pid_str])
157 .output();
158 }
159 }
160 child.start_kill().map_err(|_| crate::error::ProcessKillError)?;
161 Ok(())
162 } else {
163 Err(crate::error::ProcessKillError)
164 }
165 }
166}
167
168impl Drop for Process {
169 fn drop(&mut self) {
170 let _ = self.kill();
171 }
172}