venus_core/execute/
windows_dll.rs1use std::collections::HashMap;
31use std::fs;
32use std::io;
33use std::path::{Path, PathBuf};
34use std::time::{Duration, SystemTime};
35
36#[cfg(windows)]
37use uuid::Uuid;
38
39pub struct WindowsDllHandler {
44 temp_dir: PathBuf,
46
47 active_copies: HashMap<PathBuf, PathBuf>,
50
51 max_age: Duration,
53}
54
55impl WindowsDllHandler {
56 pub fn new(temp_dir: PathBuf) -> Self {
62 Self {
63 temp_dir,
64 active_copies: HashMap::new(),
65 max_age: Duration::from_secs(3600), }
67 }
68
69 pub fn with_max_age(mut self, max_age: Duration) -> Self {
71 self.max_age = max_age;
72 self
73 }
74
75 pub fn prepare_for_load(&mut self, dll_path: &Path) -> io::Result<PathBuf> {
88 #[cfg(windows)]
89 {
90 self.create_uuid_copy(dll_path)
91 }
92
93 #[cfg(not(windows))]
94 {
95 Ok(dll_path.to_path_buf())
97 }
98 }
99
100 #[cfg(windows)]
102 fn create_uuid_copy(&mut self, dll_path: &Path) -> io::Result<PathBuf> {
103 fs::create_dir_all(&self.temp_dir)?;
105
106 let uuid = Uuid::new_v4();
108 let original_name = dll_path
109 .file_stem()
110 .and_then(|s| s.to_str())
111 .unwrap_or("cell");
112 let extension = dll_path.extension().and_then(|s| s.to_str()).unwrap_or("dll");
113
114 let temp_name = format!("{}-{}.{}", original_name, uuid, extension);
115 let temp_path = self.temp_dir.join(temp_name);
116
117 fs::copy(dll_path, &temp_path)?;
119
120 self.active_copies
122 .insert(temp_path.clone(), dll_path.to_path_buf());
123
124 tracing::debug!(
125 "Created DLL copy: {} -> {}",
126 dll_path.display(),
127 temp_path.display()
128 );
129
130 Ok(temp_path)
131 }
132
133 pub fn release(&mut self, loaded_path: &Path) {
142 self.active_copies.remove(loaded_path);
143 }
144
145 pub fn is_active(&self, path: &Path) -> bool {
147 self.active_copies.contains_key(path)
148 }
149
150 pub fn active_paths(&self) -> impl Iterator<Item = &Path> {
152 self.active_copies.keys().map(|p| p.as_path())
153 }
154
155 pub fn cleanup_old_copies(&self) -> io::Result<usize> {
165 if !self.temp_dir.exists() {
166 return Ok(0);
167 }
168
169 let cutoff = SystemTime::now()
170 .checked_sub(self.max_age)
171 .unwrap_or(SystemTime::UNIX_EPOCH);
172
173 let mut cleaned = 0;
174
175 for entry in fs::read_dir(&self.temp_dir)? {
176 let entry = entry?;
177 let path = entry.path();
178
179 if self.active_copies.contains_key(&path) {
181 continue;
182 }
183
184 let is_old = entry
186 .metadata()
187 .and_then(|m| m.modified())
188 .map(|modified| modified < cutoff)
189 .unwrap_or(false);
190
191 if is_old && fs::remove_file(&path).is_ok() {
192 tracing::debug!("Cleaned up old DLL: {}", path.display());
193 cleaned += 1;
194 }
195 }
196
197 if cleaned > 0 {
198 tracing::info!("Cleaned up {} old DLL copies", cleaned);
199 }
200
201 Ok(cleaned)
202 }
203
204 pub fn cleanup_all(&self) -> io::Result<usize> {
213 if !self.temp_dir.exists() {
214 return Ok(0);
215 }
216
217 let mut cleaned = 0;
218
219 for entry in fs::read_dir(&self.temp_dir)? {
220 let entry = entry?;
221 let path = entry.path();
222
223 if self.active_copies.contains_key(&path) {
225 continue;
226 }
227
228 let extension = path.extension().and_then(|s| s.to_str()).unwrap_or("");
230 if !matches!(extension, "dll" | "so" | "dylib") {
231 continue;
232 }
233
234 if let Ok(()) = fs::remove_file(&path) {
236 tracing::debug!("Force cleaned DLL: {}", path.display());
237 cleaned += 1;
238 }
239 }
240
241 Ok(cleaned)
242 }
243
244 pub fn temp_dir(&self) -> &Path {
246 &self.temp_dir
247 }
248}
249
250impl Default for WindowsDllHandler {
251 fn default() -> Self {
252 Self::new(PathBuf::from(".venus/build/temp"))
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259 use std::thread;
260 use tempfile::tempdir;
261
262 #[test]
263 fn test_handler_creation() {
264 let handler = WindowsDllHandler::new(PathBuf::from("/tmp/test"));
265 assert_eq!(handler.temp_dir(), Path::new("/tmp/test"));
266 assert!(handler.active_paths().next().is_none());
267 }
268
269 #[test]
270 fn test_with_max_age() {
271 let handler = WindowsDllHandler::new(PathBuf::from("/tmp/test"))
272 .with_max_age(Duration::from_secs(60));
273 assert_eq!(handler.max_age, Duration::from_secs(60));
274 }
275
276 #[test]
277 fn test_prepare_for_load_non_windows() {
278 let temp = tempdir().unwrap();
279 let mut handler = WindowsDllHandler::new(temp.path().join("temp"));
280
281 let dll_path = temp.path().join("test.so");
282 fs::write(&dll_path, b"fake dll").unwrap();
283
284 let result = handler.prepare_for_load(&dll_path).unwrap();
285
286 #[cfg(not(windows))]
288 assert_eq!(result, dll_path);
289
290 #[cfg(windows)]
292 {
293 assert_ne!(result, dll_path);
294 assert!(result.exists());
295 assert!(handler.is_active(&result));
296 }
297 }
298
299 #[test]
300 fn test_release() {
301 let temp = tempdir().unwrap();
302 let mut handler = WindowsDllHandler::new(temp.path().join("temp"));
303
304 let fake_path = temp.path().join("fake.dll");
305 handler.active_copies.insert(fake_path.clone(), PathBuf::from("original.dll"));
306
307 assert!(handler.is_active(&fake_path));
308 handler.release(&fake_path);
309 assert!(!handler.is_active(&fake_path));
310 }
311
312 #[test]
313 fn test_cleanup_old_copies() {
314 let temp = tempdir().unwrap();
315 let temp_dir = temp.path().join("temp");
316 fs::create_dir_all(&temp_dir).unwrap();
317
318 let handler = WindowsDllHandler::new(temp_dir.clone())
319 .with_max_age(Duration::from_millis(10));
320
321 let old_file = temp_dir.join("old-test.dll");
323 fs::write(&old_file, b"old").unwrap();
324
325 thread::sleep(Duration::from_millis(20));
327
328 let cleaned = handler.cleanup_old_copies().unwrap();
330
331 assert_eq!(cleaned, 1);
332 assert!(!old_file.exists());
333 }
334
335 #[test]
336 fn test_cleanup_skips_active() {
337 let temp = tempdir().unwrap();
338 let temp_dir = temp.path().join("temp");
339 fs::create_dir_all(&temp_dir).unwrap();
340
341 let mut handler = WindowsDllHandler::new(temp_dir.clone())
342 .with_max_age(Duration::from_millis(10));
343
344 let active_file = temp_dir.join("active.dll");
346 fs::write(&active_file, b"active").unwrap();
347 handler.active_copies.insert(active_file.clone(), PathBuf::from("original.dll"));
348
349 thread::sleep(Duration::from_millis(20));
351
352 let cleaned = handler.cleanup_old_copies().unwrap();
354
355 assert_eq!(cleaned, 0);
356 assert!(active_file.exists());
357 }
358
359 #[test]
360 fn test_cleanup_all() {
361 let temp = tempdir().unwrap();
362 let temp_dir = temp.path().join("temp");
363 fs::create_dir_all(&temp_dir).unwrap();
364
365 let mut handler = WindowsDllHandler::new(temp_dir.clone());
366
367 let file1 = temp_dir.join("test1.dll");
369 let file2 = temp_dir.join("test2.so");
370 let active = temp_dir.join("active.dylib");
371
372 fs::write(&file1, b"1").unwrap();
373 fs::write(&file2, b"2").unwrap();
374 fs::write(&active, b"active").unwrap();
375
376 handler.active_copies.insert(active.clone(), PathBuf::from("original.dylib"));
377
378 let cleaned = handler.cleanup_all().unwrap();
380
381 assert_eq!(cleaned, 2);
382 assert!(!file1.exists());
383 assert!(!file2.exists());
384 assert!(active.exists()); }
386}