mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-02-12 03:41:38 +00:00
fixed issues with stale processes
This commit is contained in:
parent
91bf83ea12
commit
223d288ab4
6
.github/workflows/build-and-release.yml
vendored
6
.github/workflows/build-and-release.yml
vendored
@ -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')
|
||||
|
||||
@ -238,6 +238,8 @@
|
||||
<ThirdPartyComponent Name="PDFium" Developer="Lei Zhang, Tom Sepez, Dan Sinclair, and Foxit, Google, Chromium, Collabora, Ada, DocsCorp, Dropbox, Microsoft, and PSPDFKit Teams & Open Source Community" LicenseName="Apache-2.0" LicenseUrl="https://pdfium.googlesource.com/pdfium/+/refs/heads/main/LICENSE" RepositoryUrl="https://pdfium.googlesource.com/pdfium" UseCase="@T("This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat.")"/>
|
||||
<ThirdPartyComponent Name="pdfium-render" Developer="Alastair Carey, Dorian Rudolph & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/ajrcarey/pdfium-render/blob/master/LICENSE.md" RepositoryUrl="https://github.com/ajrcarey/pdfium-render" UseCase="@T("This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat.")"/>
|
||||
<ThirdPartyComponent Name="sys-locale" Developer="1Password Team, ComplexSpaces & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/1Password/sys-locale/blob/main/LICENSE-MIT" RepositoryUrl="https://github.com/1Password/sys-locale" UseCase="@T("This library is used to determine the language of the operating system. This is necessary to set the language of the user interface.")"/>
|
||||
<ThirdPartyComponent Name="sysinfo" Developer="Guillaume Gomez & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/GuillaumeGomez/sysinfo?tab=MIT-1-ov-file" RepositoryUrl="https://github.com/GuillaumeGomez/sysinfo" UseCase="@T("This library is used to manage processes across different operating systems, ensuring proper handling of stale or zombie processes.")"/>
|
||||
<ThirdPartyComponent Name="tempfile" Developer="Steven Allen, Ashley Mannix & Open Source Community" LicenseName="Apache-2.0" LicenseUrl="https://github.com/Stebalien/tempfile?tab=Apache-2.0-1-ov-file" RepositoryUrl="https://github.com/Stebalien/tempfile" UseCase="@T("This library is used to create temporary folders for saving the certificate and private key data for communication with Qdrant.")"/>
|
||||
<ThirdPartyComponent Name="Lua-CSharp" Developer="Yusuke Nakada & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/nuskey8/Lua-CSharp/blob/main/LICENSE" RepositoryUrl="https://github.com/nuskey8/Lua-CSharp" UseCase="@T("We use Lua as the language for plugins. Lua-CSharp lets Lua scripts communicate with AI Studio and vice versa. Thank you, Yusuke Nakada, for this great library.")" />
|
||||
<ThirdPartyComponent Name="HtmlAgilityPack" Developer="ZZZ Projects & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/zzzprojects/html-agility-pack/blob/master/LICENSE" RepositoryUrl="https://github.com/zzzprojects/html-agility-pack" UseCase="@T("We use the HtmlAgilityPack to extract content from the web. This is necessary, e.g., when you provide a URL as input for an assistant.")"/>
|
||||
<ThirdPartyComponent Name="ReverseMarkdown" Developer="Babu Annamalai & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/mysticmind/reversemarkdown-net/blob/master/LICENSE" RepositoryUrl="https://github.com/mysticmind/reversemarkdown-net" UseCase="@T("This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant.")"/>
|
||||
|
||||
@ -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"] }
|
||||
|
||||
191
runtime/build.rs
191
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::<bool>() {
|
||||
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<dyn std::error::Error>> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@ -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<Mutex<Option<Window>>> = 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();
|
||||
},
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -16,4 +16,4 @@ pub mod pandoc;
|
||||
pub mod qdrant;
|
||||
pub mod certificate_factory;
|
||||
pub mod runtime_api_token;
|
||||
pub mod zombie_process_remover;
|
||||
pub mod stale_process_cleanup;
|
||||
@ -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();
|
||||
}
|
||||
@ -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<dyn Error>> {
|
||||
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<dyn Error>> {
|
||||
|
||||
94
runtime/src/stale_process_cleanup.rs
Normal file
94
runtime/src/stale_process_cleanup.rs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user