diff --git a/app/MindWork AI Studio/Pages/Information.razor b/app/MindWork AI Studio/Pages/Information.razor index bc764560..cd97d457 100644 --- a/app/MindWork AI Studio/Pages/Information.razor +++ b/app/MindWork AI Studio/Pages/Information.razor @@ -239,7 +239,7 @@ - + diff --git a/documentation/Build.md b/documentation/Build.md index 21063eef..582270e0 100644 --- a/documentation/Build.md +++ b/documentation/Build.md @@ -45,7 +45,19 @@ Do you want to test your changes before creating a PR? Follow these steps: 9. Execute the command `dotnet run`. 10. After compiling the .NET code, the app will finally start inside the Tauri runtime window. -You can now test your changes. +You can now test your changes. To stop the application: +- Close the Tauri window (GUI). +- Press ``Ctrl+C`` in the terminal where the app is running. +- Stop the process via your IDE’s run/debug controls. + +> ⚠️ Important: Stopping the app via ``Ctrl+C`` or the IDE may not terminate the Qdrant sidecar process, especially on Windows. This can lead to startup failures when restarting the app. + +If you encounter issues restarting Tauri: +Manually kill the Qdrant process: +**Linux/macOS:** Run pkill -f qdrant in your terminal. +**Windows:** Open Task Manager → Find qdrant.exe → Right-click → “End task”. + +Restart your Tauri app. ## Create a release In order to create a release: diff --git a/runtime/src/dotnet.rs b/runtime/src/dotnet.rs index c1e0b560..338074a0 100644 --- a/runtime/src/dotnet.rs +++ b/runtime/src/dotnet.rs @@ -17,6 +17,7 @@ use crate::environment::{is_dev, DATA_DIRECTORY}; use crate::network::get_available_port; use crate::runtime_api::API_SERVER_PORT; use crate::stale_process_cleanup::{kill_stale_process, log_potential_stale_process}; +use crate::sidecar_types::SidecarType; // 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 @@ -30,6 +31,7 @@ static DOTNET_SERVER_PORT: Lazy = Lazy::new(|| get_available_port().unwrap( static DOTNET_INITIALIZED: Lazy> = Lazy::new(|| Mutex::new(false)); pub const PID_FILE_NAME: &str = "mindwork_ai_studio.pid"; +const SIDECAR_TYPE:SidecarType = SidecarType::Dotnet; /// Returns the desired port of the .NET server. Our .NET app calls this endpoint to get /// the port where the .NET server should listen to. @@ -100,7 +102,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_stale_process(Path::new(DATA_DIRECTORY.get().unwrap()).join(PID_FILE_NAME), server_pid); + log_potential_stale_process(Path::new(DATA_DIRECTORY.get().unwrap()).join(PID_FILE_NAME), server_pid, SIDECAR_TYPE); // Save the server process to stop it later: *server_spawn_clone.lock().unwrap() = Some(child); @@ -165,7 +167,7 @@ pub fn stop_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_stale_process(pid_path) { + if let Err(e) = kill_stale_process(pid_path, SIDECAR_TYPE) { 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 5447395b..1b13e099 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -16,4 +16,5 @@ pub mod pandoc; pub mod qdrant; pub mod certificate_factory; pub mod runtime_api_token; -pub mod stale_process_cleanup; \ No newline at end of file +pub mod stale_process_cleanup; +mod sidecar_types; \ No newline at end of file diff --git a/runtime/src/qdrant.rs b/runtime/src/qdrant.rs index c562dc75..41429431 100644 --- a/runtime/src/qdrant.rs +++ b/runtime/src/qdrant.rs @@ -17,6 +17,7 @@ use crate::certificate_factory::generate_certificate; use std::path::PathBuf; use tempfile::{TempDir, Builder}; use crate::stale_process_cleanup::{kill_stale_process, log_potential_stale_process}; +use crate::sidecar_types::SidecarType; // Qdrant server process started in a separate process and can communicate // via HTTP or gRPC with the .NET server and the runtime process @@ -39,6 +40,7 @@ static API_TOKEN: Lazy = Lazy::new(|| { static TMPDIR: Lazy>> = Lazy::new(|| Mutex::new(None)); const PID_FILE_NAME: &str = "qdrant.pid"; +const SIDECAR_TYPE:SidecarType = SidecarType::Qdrant; #[derive(Serialize)] pub struct ProvideQdrantInfo { @@ -76,7 +78,7 @@ pub fn start_qdrant_server(){ let snapshot_path = path.join("snapshots").to_str().unwrap().to_string(); let init_path = path.join(".qdrant-initalized").to_str().unwrap().to_string(); - let mut qdrant_server_environment = HashMap::from_iter([ + let qdrant_server_environment = HashMap::from_iter([ (String::from("QDRANT__SERVICE__HTTP_PORT"), QDRANT_SERVER_PORT_HTTP.to_string()), (String::from("QDRANT__SERVICE__GRPC_PORT"), QDRANT_SERVER_PORT_GRPC.to_string()), (String::from("QDRANT_INIT_FILE_PATH"), init_path), @@ -90,13 +92,6 @@ pub fn start_qdrant_server(){ let server_spawn_clone = QDRANT_SERVER.clone(); tauri::async_runtime::spawn(async move { - #[cfg(target_os = "macos")] - { - qdrant_server_environment.insert( - "MALLOC_CONF".to_string(), - "background_thread:false".to_string(), - ); - } let (mut rx, child) = Command::new_sidecar("qdrant") .expect("Failed to create sidecar for Qdrant") .args(["--config-path", "resources/databases/qdrant/config.yaml"]) @@ -106,7 +101,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_stale_process(path.join(PID_FILE_NAME), server_pid); + log_potential_stale_process(path.join(PID_FILE_NAME), server_pid, SIDECAR_TYPE); // Save the server process to stop it later: *server_spawn_clone.lock().unwrap() = Some(child); @@ -193,7 +188,7 @@ pub fn drop_tmpdir() { /// Remove old Pid files and kill the corresponding processes pub fn cleanup_qdrant() { let pid_path = Path::new(DATA_DIRECTORY.get().unwrap()).join("databases").join("qdrant").join(PID_FILE_NAME); - if let Err(e) = kill_stale_process(pid_path) { + if let Err(e) = kill_stale_process(pid_path, SIDECAR_TYPE) { warn!(Source = "Qdrant"; "Error during the cleanup of Qdrant: {}", e); } if let Err(e) = delete_old_certificates() { diff --git a/runtime/src/sidecar_types.rs b/runtime/src/sidecar_types.rs new file mode 100644 index 00000000..7e5bfde0 --- /dev/null +++ b/runtime/src/sidecar_types.rs @@ -0,0 +1,15 @@ +use std::fmt; + +pub enum SidecarType { + Dotnet, + Qdrant, +} + +impl fmt::Display for SidecarType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SidecarType::Dotnet => write!(f, ".Net"), + SidecarType::Qdrant => write!(f, "Qdrant"), + } + } +} \ No newline at end of file diff --git a/runtime/src/stale_process_cleanup.rs b/runtime/src/stale_process_cleanup.rs index 124f576c..7d177ac8 100644 --- a/runtime/src/stale_process_cleanup.rs +++ b/runtime/src/stale_process_cleanup.rs @@ -4,6 +4,7 @@ use std::io::{Error, ErrorKind, Write}; use std::path::{PathBuf}; use log::{info, warn}; use sysinfo::{Pid, ProcessesToUpdate, Signal, System}; +use crate::sidecar_types::SidecarType; fn parse_pid_file(content: &str) -> Result<(u32, String), Error> { let mut lines = content @@ -23,7 +24,7 @@ fn parse_pid_file(content: &str) -> Result<(u32, String), Error> { Ok((pid, name)) } -pub fn kill_stale_process(pid_file_path: PathBuf) -> Result<(), Error> { +pub fn kill_stale_process(pid_file_path: PathBuf, sidecar_type: SidecarType) -> Result<(), Error> { if !pid_file_path.exists() { return Ok(()); } @@ -51,30 +52,24 @@ pub fn kill_stale_process(pid_file_path: PathBuf) -> Result<(), Error> { 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()); + info!(Source="Stale Process Cleanup";"{}: Killed process: \"{}\"", sidecar_type,pid_file_path.display()); } else { - info!("Pid file {} was found, but process was not.", pid); + info!(Source="Stale Process Cleanup";"{}: Pid file with process number '{}' was found, but process was not.", sidecar_type, pid); }; fs::remove_file(&pid_file_path)?; - info!("Deleted redundant Pid file: {}", pid_file_path.display()); + info!(Source="Stale Process Cleanup";"{}: Deleted redundant Pid file: \"{}\"", sidecar_type,pid_file_path.display()); Ok(()) } -pub fn log_potential_stale_process(pid_file_path: PathBuf, pid: u32) { +pub fn log_potential_stale_process(pid_file_path: PathBuf, pid: u32, sidecar_type: SidecarType) { let mut system = System::new_all(); - let pid_u32 = pid; - let pid = Pid::from_u32(pid_u32); + let pid = Pid::from_u32(pid); 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 + warn!(Source="Stale Process Cleanup"; + "{}: Pid file with process number '{}' was not created because the process was not found.", + sidecar_type, pid ); return; }; @@ -82,13 +77,13 @@ pub fn log_potential_stale_process(pid_file_path: PathBuf, pid: u32) { match File::create(&pid_file_path) { Ok(mut file) => { let name = process.name().to_string_lossy(); - let content = format!("{pid_u32}\n{name}\n"); + let content = format!("{pid}\n{name}\n"); if let Err(e) = file.write_all(content.as_bytes()) { - warn!("Failed to write to {}: {}", pid_file_path.display(), e); + warn!(Source="Stale Process Cleanup";"{}: Failed to write to \"{}\": {}", sidecar_type,pid_file_path.display(), e); } } Err(e) => { - warn!("Failed to create {}: {}", pid_file_path.display(), e); + warn!(Source="Stale Process Cleanup";"{}: Failed to create \"{}\": {}", sidecar_type, pid_file_path.display(), e); } } }