From 223d288ab40e53ee4cfa085149bbe801bbd86c31 Mon Sep 17 00:00:00 2001 From: PaulKoudelka Date: Fri, 30 Jan 2026 17:59:15 +0100 Subject: [PATCH] fixed issues with stale processes --- .github/workflows/build-and-release.yml | 6 +- .../Pages/Information.razor | 2 + runtime/Cargo.toml | 3 +- runtime/build.rs | 191 +----------------- runtime/src/app_window.rs | 30 +-- runtime/src/dotnet.rs | 7 +- runtime/src/lib.rs | 2 +- runtime/src/main.rs | 10 - runtime/src/qdrant.rs | 20 +- runtime/src/stale_process_cleanup.rs | 94 +++++++++ runtime/src/zombie_process_remover.rs | 145 ------------- 11 files changed, 136 insertions(+), 374 deletions(-) create mode 100644 runtime/src/stale_process_cleanup.rs delete mode 100644 runtime/src/zombie_process_remover.rs diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 329b58f6..091faafb 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -2,7 +2,7 @@ name: Build and Release on: push: branches: - - "**" + - main tags: - "v*.*.*" @@ -631,7 +631,7 @@ jobs: cd runtime export TAURI_PRIVATE_KEY="$PRIVATE_PUBLISH_KEY" export TAURI_KEY_PASSWORD="$PRIVATE_PUBLISH_KEY_PASSWORD" - cargo tauri build --target ${{ matrix.rust_target }} --bundles none + cargo tauri build --target ${{ matrix.rust_target }} --bundles ${{ matrix.tauri_bundle }} - name: Build Tauri project (Windows) if: matrix.platform == 'windows-latest' @@ -642,7 +642,7 @@ jobs: cd runtime $env:TAURI_PRIVATE_KEY="$env:PRIVATE_PUBLISH_KEY" $env:TAURI_KEY_PASSWORD="$env:PRIVATE_PUBLISH_KEY_PASSWORD" - cargo tauri build --target ${{ matrix.rust_target }} --bundles none + cargo tauri build --target ${{ matrix.rust_target }} --bundles ${{ matrix.tauri_bundle }} - name: Upload artifact (macOS) if: startsWith(matrix.platform, 'macos') && startsWith(github.ref, 'refs/tags/v') diff --git a/app/MindWork AI Studio/Pages/Information.razor b/app/MindWork AI Studio/Pages/Information.razor index 54087f16..8d33571f 100644 --- a/app/MindWork AI Studio/Pages/Information.razor +++ b/app/MindWork AI Studio/Pages/Information.razor @@ -238,6 +238,8 @@ + + diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 9fc375cd..b9a7dfda 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -7,7 +7,6 @@ authors = ["Thorsten Sommer"] [build-dependencies] tauri-build = { version = "1.5", features = [] } -dirs = "6.0.0" [dependencies] tauri = { version = "1.8", features = [ "http-all", "updater", "shell-sidecar", "shell-open", "dialog", "global-shortcut"] } @@ -42,6 +41,7 @@ cfg-if = "1.0.4" pptx-to-md = "0.4.0" tempfile = "3.8" strum_macros = "0.27" +sysinfo = "0.38.0" # Fixes security vulnerability downstream, where the upstream is not fixed yet: url = "2.5.8" @@ -50,6 +50,7 @@ crossbeam-channel = "0.5.15" tracing-subscriber = "0.3.20" dirs = "6.0.0" + [target.'cfg(target_os = "linux")'.dependencies] # See issue https://github.com/tauri-apps/tauri/issues/4470 reqwest = { version = "0.13.1", features = ["native-tls-vendored"] } diff --git a/runtime/build.rs b/runtime/build.rs index e90dbed8..c4d1f749 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -1,32 +1,6 @@ -use std::{env, fs}; use std::path::{PathBuf}; -use std::process::Command; -use std::io::{Error, ErrorKind}; fn main() { - match env::var("MINDWORK_START_DEV_ENV") { - Ok(val) => { - let is_started_manually = match val.parse::() { - Ok(b) => b, - Err(_) => { - println!("cargo: warning= Invalid value for MINDWORK_START_DEV_ENV: expected 'true' or 'false'"); - return; - } - }; - if is_started_manually { - if let Err(e) = kill_zombie_qdrant_process(){ - println!("cargo:warning=Error: {e}"); - return; - }; - if let Err(e) = delete_old_certificates() { - println!("cargo: warning= Failed to delete old certificates: {e}"); - } - } - }, - Err(_) => { - println!("cargo: warning= The environment variable 'MINDWORK_START_DEV_ENV' was not found."); - } - } tauri_build::build(); let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); @@ -109,167 +83,4 @@ fn update_tauri_conf(tauri_conf_path: &str, version: &str) { } std::fs::write(tauri_conf_path, new_tauri_conf).unwrap(); -} - -#[cfg(unix)] -pub fn ensure_process_killed(pid: u32, expected_name: &str) -> Result<(), Error> { - // - // Check if PID exists and name matches - // - let ps_output = Command::new("ps") - .arg("-p") - .arg(pid.to_string()) - .arg("-o") - .arg("comm=") - .output()?; - - let output = String::from_utf8_lossy(&ps_output.stdout).trim().to_string(); - - if output.is_empty() { - // Process doesn't exist - return Ok(()); - } - - let name = output; - if name != expected_name { - return Err(Error::new(ErrorKind::InvalidInput, "Process name does not match")); - } - - // - // Kill the process - // - let kill_output = Command::new("kill") - .arg("-9") - .arg(pid.to_string()) - .output()?; - - if !kill_output.status.success() { - return Err(Error::new(ErrorKind::Other, "Failed to kill process")); - } - - // - // Verify process is killed - // - let ps_check = Command::new("ps") - .arg("-p") - .arg(pid.to_string()) - .output()?; - - let output = String::from_utf8_lossy(&ps_check.stdout).trim().to_string(); - if output.is_empty() { - Ok(()) - } else { - Err(Error::new(ErrorKind::Other, "Process still running after kill attempt")) - } -} - -#[cfg(windows)] -pub fn ensure_process_killed(pid: u32, expected_name: &str) -> Result<(), Error> { - // - // Check if PID exists and name matches - // - let tasklist_output = Command::new("tasklist") - .arg("/FI") - .arg(format!("PID eq {}", pid)) - .arg("/FO") - .arg("CSV") - .arg("/NH") - .output()?; - - let output = String::from_utf8_lossy(&tasklist_output.stdout).trim().to_string(); - - if output.is_empty() || !output.starts_with('"') { - println!("cargo:warning= Pid file was found, but process was not."); - return Ok(()) - } - - let name = output.split(',').next().unwrap_or("").trim_matches('"'); - if name != expected_name { - return Err(Error::new(ErrorKind::InvalidInput, format!("Process name does not match. Expected:{}, got:{}",expected_name,name))); - } - - // - // Kill the process - // - let kill_output = Command::new("taskkill") - .arg("/PID") - .arg(pid.to_string()) - .arg("/F") - .arg("/T") - .output()?; - - if !kill_output.status.success() { - return Err(Error::new(ErrorKind::Other, "Failed to kill process")); - } - - // - // Verify process is killed - // - let tasklist_check = Command::new("tasklist") - .arg("/FI") - .arg(format!("PID eq {}", pid)) - .output()?; - - let output = String::from_utf8_lossy(&tasklist_check.stdout).trim().to_string(); - if output.is_empty() || !output.starts_with('"') { - Ok(()) - } - else { - Err(Error::new(ErrorKind::Other, "Process still running after kill attempt")) - } -} - - -pub fn kill_zombie_qdrant_process() -> Result<(), Error> { - let pid_file = dirs::data_local_dir() - .expect("Local appdata was not found") - .join("com.github.mindwork-ai.ai-studio") - .join("data") - .join("databases") - .join("qdrant") - .join("qdrant.pid"); - - if !pid_file.exists() { - return Ok(()); - } - - let pid_str = fs::read_to_string(&pid_file)?; - let pid: u32 = pid_str.trim().parse().map_err(|_| {Error::new(ErrorKind::InvalidData, "Invalid PID in file")})?; - if let Err(e) = ensure_process_killed(pid, "qdrant.exe".as_ref()){ - return Err(e); - } - - fs::remove_file(&pid_file)?; - println!("cargo:warning= Killed qdrant process and deleted redundant Pid file: {}", pid_file.display()); - - Ok(()) -} - -pub fn delete_old_certificates() -> Result<(), Box> { - let dir_path = dirs::data_local_dir() - .expect("Local appdata was not found") - .join("com.github.mindwork-ai.ai-studio") - .join("data") - .join("databases") - .join("qdrant"); - - if !dir_path.exists() { - return Ok(()); - } - - for entry in fs::read_dir(dir_path)? { - let entry = entry?; - let path = entry.path(); - - if path.is_dir() { - let file_name = entry.file_name(); - let folder_name = file_name.to_string_lossy(); - - if folder_name.starts_with("cert-") { - fs::remove_dir_all(&path)?; - println!("cargo: warning= Removed old certificates in: {}", path.display()); - } - } - } - Ok(()) -} +} \ No newline at end of file diff --git a/runtime/src/app_window.rs b/runtime/src/app_window.rs index df48fc2b..c7e7bd74 100644 --- a/runtime/src/app_window.rs +++ b/runtime/src/app_window.rs @@ -15,11 +15,13 @@ use tauri::api::dialog::blocking::FileDialogBuilder; use tokio::sync::broadcast; use tokio::time; use crate::api_token::APIToken; -use crate::dotnet::{cleanup_dotnet_server, stop_dotnet_server}; +use crate::dotnet::{cleanup_dotnet_server, start_dotnet_server, stop_dotnet_server}; use crate::environment::{is_prod, is_dev, CONFIG_DIRECTORY, DATA_DIRECTORY}; use crate::log::switch_to_file_logging; use crate::pdfium::PDFIUM_LIB_PATH; use crate::qdrant::{cleanup_qdrant, start_qdrant_server, stop_qdrant_server}; +#[cfg(debug_assertions)] +use crate::dotnet::create_startup_env_file; /// The Tauri main window. static MAIN_WINDOW: Lazy>> = Lazy::new(|| Mutex::new(None)); @@ -102,20 +104,24 @@ pub fn start_tauri() { let data_path = data_path.join("data"); // Get and store the data and config directories: - DATA_DIRECTORY.set(data_path.to_str().unwrap().to_string()).map_err(|_| error!("Was not abe to set the data directory.")).unwrap(); + DATA_DIRECTORY.set(data_path.to_str().unwrap().to_string()).map_err(|_| error!("Was not able to set the data directory.")).unwrap(); CONFIG_DIRECTORY.set(app.path_resolver().app_config_dir().unwrap().to_str().unwrap().to_string()).map_err(|_| error!("Was not able to set the config directory.")).unwrap(); - if is_prod() { - cleanup_qdrant().expect("Zombie processes of Qdrant were not killed"); - cleanup_dotnet_server(); + cleanup_qdrant(); + cleanup_dotnet_server(); + + if is_dev() { + #[cfg(debug_assertions)] + create_startup_env_file(); + } else { + start_dotnet_server(); } + start_qdrant_server(); info!(Source = "Bootloader Tauri"; "Reconfigure the file logger to use the app data directory {data_path:?}"); switch_to_file_logging(data_path).map_err(|e| error!("Failed to switch logging to file: {e}")).unwrap(); set_pdfium_path(app.path_resolver()); - start_qdrant_server(); - Ok(()) }) .plugin(tauri_plugin_window_state::Builder::default().build()) @@ -164,6 +170,7 @@ pub fn start_tauri() { if is_prod() { stop_dotnet_server(); + stop_qdrant_server(); } else { warn!(Source = "Tauri"; "Development environment detected; do not stop the .NET server."); } @@ -193,6 +200,10 @@ pub fn start_tauri() { RunEvent::ExitRequested { .. } => { warn!(Source = "Tauri"; "Run event: exit was requested."); stop_qdrant_server(); + if is_prod() { + warn!("Try to stop the .NET server as well..."); + stop_dotnet_server(); + } } RunEvent::Ready => { @@ -204,10 +215,6 @@ pub fn start_tauri() { }); warn!(Source = "Tauri"; "Tauri app was stopped."); - if is_prod() { - warn!("Try to stop the .NET server as well..."); - stop_dotnet_server(); - } } /// Our event API endpoint for Tauri events. We try to send an endless stream of events to the client. @@ -458,7 +465,6 @@ pub async fn install_update(_token: APIToken) { let cloned_response_option = CHECK_UPDATE_RESPONSE.lock().unwrap().clone(); match cloned_response_option { Some(update_response) => { - stop_qdrant_server(); update_response.download_and_install().await.unwrap(); }, diff --git a/runtime/src/dotnet.rs b/runtime/src/dotnet.rs index 944e1d95..c1e0b560 100644 --- a/runtime/src/dotnet.rs +++ b/runtime/src/dotnet.rs @@ -16,7 +16,7 @@ use crate::encryption::ENCRYPTION; use crate::environment::{is_dev, DATA_DIRECTORY}; use crate::network::get_available_port; use crate::runtime_api::API_SERVER_PORT; -use crate::zombie_process_remover::{kill_zombie_process, log_potential_zombie_process}; +use crate::stale_process_cleanup::{kill_stale_process, log_potential_stale_process}; // The .NET server is started in a separate process and communicates with this // runtime process via IPC. However, we do net start the .NET server in @@ -100,7 +100,7 @@ pub fn start_dotnet_server() { .expect("Failed to spawn .NET server process."); let server_pid = child.pid(); info!(Source = "Bootloader .NET"; "The .NET server process started with PID={server_pid}."); - log_potential_zombie_process(Path::new(DATA_DIRECTORY.get().unwrap()).join(PID_FILE_NAME), server_pid.to_string().as_str()); + log_potential_stale_process(Path::new(DATA_DIRECTORY.get().unwrap()).join(PID_FILE_NAME), server_pid); // Save the server process to stop it later: *server_spawn_clone.lock().unwrap() = Some(child); @@ -158,13 +158,14 @@ pub fn stop_dotnet_server() { } else { warn!("The .NET server process was not started or is already stopped."); } + info!("Start dotnet server cleanup"); cleanup_dotnet_server(); } /// Remove old Pid files and kill the corresponding processes pub fn cleanup_dotnet_server() { let pid_path = Path::new(DATA_DIRECTORY.get().unwrap()).join(PID_FILE_NAME); - if let Err(e) = kill_zombie_process(pid_path, "mindworkAIStudioServer.exe"){ + if let Err(e) = kill_stale_process(pid_path) { warn!(Source = ".NET"; "Error during the cleanup of .NET: {}", e); } } \ No newline at end of file diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 65b6abdf..5447395b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -16,4 +16,4 @@ pub mod pandoc; pub mod qdrant; pub mod certificate_factory; pub mod runtime_api_token; -pub mod zombie_process_remover; \ No newline at end of file +pub mod stale_process_cleanup; \ No newline at end of file diff --git a/runtime/src/main.rs b/runtime/src/main.rs index 91427472..00a7ba90 100644 --- a/runtime/src/main.rs +++ b/runtime/src/main.rs @@ -7,14 +7,11 @@ extern crate core; use log::{info, warn}; use mindwork_ai_studio::app_window::start_tauri; use mindwork_ai_studio::runtime_certificate::{generate_runtime_certificate}; -use mindwork_ai_studio::dotnet::start_dotnet_server; use mindwork_ai_studio::environment::is_dev; use mindwork_ai_studio::log::init_logging; use mindwork_ai_studio::metadata::MetaData; use mindwork_ai_studio::runtime_api::start_runtime_api; -#[cfg(debug_assertions)] -use mindwork_ai_studio::dotnet::create_startup_env_file; #[tokio::main] async fn main() { @@ -49,12 +46,5 @@ async fn main() { generate_runtime_certificate(); start_runtime_api(); - if is_dev() { - #[cfg(debug_assertions)] - create_startup_env_file(); - } else { - start_dotnet_server(); - } - start_tauri(); } \ No newline at end of file diff --git a/runtime/src/qdrant.rs b/runtime/src/qdrant.rs index 523a3c2a..c562dc75 100644 --- a/runtime/src/qdrant.rs +++ b/runtime/src/qdrant.rs @@ -16,7 +16,7 @@ use crate::environment::DATA_DIRECTORY; use crate::certificate_factory::generate_certificate; use std::path::PathBuf; use tempfile::{TempDir, Builder}; -use crate::zombie_process_remover::{kill_zombie_process, log_potential_zombie_process}; +use crate::stale_process_cleanup::{kill_stale_process, log_potential_stale_process}; // Qdrant server process started in a separate process and can communicate // via HTTP or gRPC with the .NET server and the runtime process @@ -106,7 +106,7 @@ pub fn start_qdrant_server(){ let server_pid = child.pid(); info!(Source = "Bootloader Qdrant"; "Qdrant server process started with PID={server_pid}."); - log_potential_zombie_process(path.join(PID_FILE_NAME), server_pid.to_string().as_str()); + log_potential_stale_process(path.join(PID_FILE_NAME), server_pid); // Save the server process to stop it later: *server_spawn_clone.lock().unwrap() = Some(child); @@ -150,9 +150,7 @@ pub fn stop_qdrant_server() { } drop_tmpdir(); - if let Err(e) = cleanup_qdrant(){ - warn!(Source = "Qdrant"; "Error during the cleanup of Qdrant: {}", e); - } + cleanup_qdrant(); } /// Create temporary directory with TLS relevant files @@ -193,11 +191,15 @@ pub fn drop_tmpdir() { } /// Remove old Pid files and kill the corresponding processes -pub fn cleanup_qdrant() -> Result<(), Box> { +pub fn cleanup_qdrant() { let pid_path = Path::new(DATA_DIRECTORY.get().unwrap()).join("databases").join("qdrant").join(PID_FILE_NAME); - kill_zombie_process(pid_path, "qdrant.exe")?; - delete_old_certificates()?; - Ok(()) + if let Err(e) = kill_stale_process(pid_path) { + warn!(Source = "Qdrant"; "Error during the cleanup of Qdrant: {}", e); + } + if let Err(e) = delete_old_certificates() { + warn!(Source = "Qdrant"; "Error during the cleanup of Qdrant: {}", e); + } + } pub fn delete_old_certificates() -> Result<(), Box> { diff --git a/runtime/src/stale_process_cleanup.rs b/runtime/src/stale_process_cleanup.rs new file mode 100644 index 00000000..124f576c --- /dev/null +++ b/runtime/src/stale_process_cleanup.rs @@ -0,0 +1,94 @@ +use std::fs; +use std::fs::File; +use std::io::{Error, ErrorKind, Write}; +use std::path::{PathBuf}; +use log::{info, warn}; +use sysinfo::{Pid, ProcessesToUpdate, Signal, System}; + +fn parse_pid_file(content: &str) -> Result<(u32, String), Error> { + let mut lines = content + .lines() + .map(|line| line.trim()) + .filter(|line| !line.is_empty()); + let pid_str = lines + .next() + .ok_or_else(|| Error::new(ErrorKind::InvalidData, "Missing PID in file"))?; + let pid: u32 = pid_str + .parse() + .map_err(|_| Error::new(ErrorKind::InvalidData, "Invalid PID in file"))?; + let name = lines + .next() + .ok_or_else(|| Error::new(ErrorKind::InvalidData, "Missing process name in file"))? + .to_string(); + Ok((pid, name)) +} + +pub fn kill_stale_process(pid_file_path: PathBuf) -> Result<(), Error> { + if !pid_file_path.exists() { + return Ok(()); + } + + let pid_file_content = fs::read_to_string(&pid_file_path)?; + let (pid, expected_name) = parse_pid_file(&pid_file_content)?; + + let mut system = System::new_all(); + + let pid = Pid::from_u32(pid); + system.refresh_processes(ProcessesToUpdate::Some(&[pid]), true); + if let Some(process) = system.process(pid){ + let name = process.name().to_string_lossy(); + if name != expected_name { + return Err(Error::new( + ErrorKind::InvalidInput, + format!( + "Process name does not match: expected '{}' but found '{}'", + expected_name, name + ), + )); + } + + let killed = process.kill_with(Signal::Kill).unwrap_or_else(|| process.kill()); + if !killed { + return Err(Error::new(ErrorKind::Other, "Failed to kill process")); + } + + system.refresh_processes(ProcessesToUpdate::Some(&[pid]), true); + if !system.process(pid).is_none() { + return Err(Error::new(ErrorKind::Other, "Process still running after kill attempt")) + } + info!("Killed process: {}", pid_file_path.display()); + } else { + info!("Pid file {} was found, but process was not.", pid); + }; + + fs::remove_file(&pid_file_path)?; + info!("Deleted redundant Pid file: {}", pid_file_path.display()); + Ok(()) +} + +pub fn log_potential_stale_process(pid_file_path: PathBuf, pid: u32) { + let mut system = System::new_all(); + let pid_u32 = pid; + let pid = Pid::from_u32(pid_u32); + system.refresh_processes(ProcessesToUpdate::Some(&[pid]), true); + let Some(process) = system.process(pid) else { + warn!( + "Pid file {} was not created because the process was not found.", + pid_u32 + ); + return; + }; + + match File::create(&pid_file_path) { + Ok(mut file) => { + let name = process.name().to_string_lossy(); + let content = format!("{pid_u32}\n{name}\n"); + if let Err(e) = file.write_all(content.as_bytes()) { + warn!("Failed to write to {}: {}", pid_file_path.display(), e); + } + } + Err(e) => { + warn!("Failed to create {}: {}", pid_file_path.display(), e); + } + } +} diff --git a/runtime/src/zombie_process_remover.rs b/runtime/src/zombie_process_remover.rs deleted file mode 100644 index 33d2ba74..00000000 --- a/runtime/src/zombie_process_remover.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::fs; -use std::fs::File; -use std::io::{Error, ErrorKind, Write}; -use std::path::PathBuf; -use std::process::Command; -use log::{info, warn}; - -#[cfg(unix)] -pub fn ensure_process_killed(pid: u32, expected_name: &str) -> Result<(), Error> { - // - // Check if PID exists and name matches - // - let ps_output = Command::new("ps") - .arg("-p") - .arg(pid.to_string()) - .arg("-o") - .arg("comm=") - .output()?; - - let output = String::from_utf8_lossy(&ps_output.stdout).trim().to_string(); - - if output.is_empty() { - info!("Pid file {} was found, but process was not.", pid); - return Ok(()); - } - - let name = output; - if name != expected_name { - return Err(Error::new(ErrorKind::InvalidInput, "Process name does not match")); - } - - // - // Kill the process - // - let kill_output = Command::new("kill") - .arg("-9") - .arg(pid.to_string()) - .output()?; - - if !kill_output.status.success() { - return Err(Error::new(ErrorKind::Other, "Failed to kill process")); - } - - // - // Verify process is killed - // - let ps_check = Command::new("ps") - .arg("-p") - .arg(pid.to_string()) - .output()?; - - let output = String::from_utf8_lossy(&ps_check.stdout).trim().to_string(); - if output.is_empty() { - Ok(()) - } else { - Err(Error::new(ErrorKind::Other, "Process still running after kill attempt")) - } -} - -#[cfg(windows)] -pub fn ensure_process_killed(pid: u32, expected_name: &str) -> Result<(), Error> { - // - // Check if PID exists and name matches - // - let tasklist_output = Command::new("tasklist") - .arg("/FI") - .arg(format!("PID eq {}", pid)) - .arg("/FO") - .arg("CSV") - .arg("/NH") - .output()?; - - let output = String::from_utf8_lossy(&tasklist_output.stdout).trim().to_string(); - - if output.is_empty() || !output.starts_with('"') { - info!("Pid file {} was found, but process was not.", pid); - return Ok(()) - } - - let name = output.split(',').next().unwrap_or("").trim_matches('"'); - if name != expected_name { - return Err(Error::new(ErrorKind::InvalidInput, format!("Process name does not match. Expected:{}, got:{}",expected_name,name))); - } - - // - // Kill the process - // - let kill_output = Command::new("taskkill") - .arg("/PID") - .arg(pid.to_string()) - .arg("/F") - .arg("/T") - .output()?; - - if !kill_output.status.success() { - return Err(Error::new(ErrorKind::Other, "Failed to kill process")); - } - - // - // Verify process is killed - // - let tasklist_check = Command::new("tasklist") - .arg("/FI") - .arg(format!("PID eq {}", pid)) - .output()?; - - let output = String::from_utf8_lossy(&tasklist_check.stdout).trim().to_string(); - if output.is_empty() || !output.starts_with('"') { - Ok(()) - } - else { - Err(Error::new(ErrorKind::Other, "Process still running after kill attempt")) - } -} - - -pub fn kill_zombie_process(pid_file_path: PathBuf, process_name: &str) -> Result<(), Error> { - if !pid_file_path.exists() { - return Ok(()); - } - - let pid_str = fs::read_to_string(&pid_file_path)?; - let pid: u32 = pid_str.trim().parse().map_err(|_| {Error::new(ErrorKind::InvalidData, "Invalid PID in file")})?; - if let Err(e) = ensure_process_killed(pid, process_name){ - return Err(e); - } - - fs::remove_file(&pid_file_path)?; - info!("Killed qdrant process and deleted redundant Pid file: {}", pid_file_path.display()); - - Ok(()) -} - -pub fn log_potential_zombie_process(pid_file_path: PathBuf, content: &str) { - match File::create(&pid_file_path) { - Ok(mut file) => { - if let Err(e) = file.write_all(content.as_bytes()) { - warn!("Failed to write to {}: {}", pid_file_path.display(), e); - } - } - Err(e) => { - warn!("Failed to create {}: {}", pid_file_path.display(), e); - } - } -} \ No newline at end of file