fix: Update server process handling for Windows and Unix systems

This commit is contained in:
Akarshan 2025-06-23 08:58:05 +05:30 committed by Louis
parent 7dbc2c3af2
commit 01d49a4b28
No known key found for this signature in database
GPG Key ID: 44FA9F4D33C37DE2
3 changed files with 64 additions and 34 deletions

View File

@ -57,6 +57,8 @@ hmac = "0.12.1"
sha2 = "0.10.9" sha2 = "0.10.9"
base64 = "0.22.1" base64 = "0.22.1"
libloading = "0.8.7" libloading = "0.8.7"
thiserror = "2.0.12"
nix = "=0.30.1"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
libc = "0.2.172" libc = "0.2.172"
@ -65,4 +67,3 @@ libc = "0.2.172"
tauri-plugin-updater = "2" tauri-plugin-updater = "2"
once_cell = "1.18" once_cell = "1.18"
tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] } tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] }
thiserror = "2.0.12"

View File

@ -7,6 +7,8 @@ use tauri::State; // Import Manager trait
use thiserror; use thiserror;
use tokio::process::Command; use tokio::process::Command;
use uuid::Uuid; use uuid::Uuid;
use std::time::Duration;
use tokio::time::timeout;
use crate::core::state::AppState; use crate::core::state::AppState;
@ -131,6 +133,10 @@ pub async fn load_llama_model(
// Optional: Redirect stdio if needed (e.g., for logging within Jan) // Optional: Redirect stdio if needed (e.g., for logging within Jan)
// command.stdout(Stdio::piped()); // command.stdout(Stdio::piped());
// command.stderr(Stdio::piped()); // command.stderr(Stdio::piped());
#[cfg(windows)]
{
command.creation_flags(CREATE_NEW_PROCESS_GROUP);
}
// Spawn the child process // Spawn the child process
let child = command.spawn().map_err(ServerError::Io)?; let child = command.spawn().map_err(ServerError::Io)?;
@ -163,48 +169,70 @@ pub async fn unload_llama_model(
pid: String, pid: String,
state: State<'_, AppState>, state: State<'_, AppState>,
) -> ServerResult<UnloadResult> { ) -> ServerResult<UnloadResult> {
let mut process_map = state.llama_server_process.lock().await; let mut map = state.llama_server_process.lock().await;
match process_map.remove(&pid) { if let Some(mut child) = map.remove(&pid) {
Some(mut child) => { #[cfg(unix)]
log::info!("Terminating server process with PID: {}", pid); {
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
// 1. Send the kill signal if let Some(raw_pid) = child.id() {
if let Err(e) = child.kill().await { let raw_pid = raw_pid as i32;
log::error!("Failed to send kill to server process: {}", e); log::info!("Sending SIGTERM to PID {}", raw_pid);
return Ok(UnloadResult { let _ = kill(Pid::from_raw(raw_pid), Signal::SIGTERM);
success: false,
error: Some(format!("kill failed: {}", e)),
});
}
// 2. Await its exit so the OS can reap it match timeout(Duration::from_secs(5), child.wait()).await {
Ok(Ok(status)) => log::info!("Process exited gracefully: {}", status),
Ok(Err(e)) => log::error!("Error waiting after SIGTERM: {}", e),
Err(_) => {
log::warn!("SIGTERM timed out; sending SIGKILL to PID {}", raw_pid);
let _ = kill(Pid::from_raw(raw_pid), Signal::SIGKILL);
match child.wait().await { match child.wait().await {
Ok(exit_status) => { Ok(s) => log::info!("Force-killed process exited: {}", s),
log::info!("Server exited with: {}", exit_status); Err(e) => log::error!("Error waiting after SIGKILL: {}", e),
Ok(UnloadResult {
success: true,
error: None,
})
}
Err(e) => {
log::error!("Error waiting for server process: {}", e);
Ok(UnloadResult {
success: false,
error: Some(format!("wait failed: {}", e)),
})
} }
} }
} }
None => {
log::warn!("No server with PID '{}' found to unload", pid);
Ok(UnloadResult {
success: true,
error: None,
})
} }
} }
#[cfg(windows)]
{
use windows_sys::Win32::System::Console::{GenerateConsoleCtrlEvent, CTRL_C_EVENT};
use windows_sys::Win32::Foundation::BOOL;
if let Some(raw_pid) = child.id() {
log::info!("Sending Ctrl-C to PID {}", raw_pid);
let ok: BOOL = unsafe { GenerateConsoleCtrlEvent(CTRL_C_EVENT, raw_pid) };
if ok == 0 {
log::error!("Failed to send Ctrl-C to PID {}", raw_pid);
}
match timeout(Duration::from_secs(5), child.wait()).await {
Ok(Ok(status)) => log::info!("Process exited after Ctrl-C: {}", status),
Ok(Err(e)) => log::error!("Error waiting after Ctrl-C: {}", e),
Err(_) => {
log::warn!("Timed out; force-killing PID {}", raw_pid);
if let Err(e) = child.kill().await {
log::error!("Failed to kill process {}: {}", raw_pid, e);
return Ok(UnloadResult { success: false, error: Some(format!("kill failed: {}", e)) });
}
if let Ok(s) = child.wait().await {
log::info!("Process finally exited: {}", s);
}
}
}
}
}
Ok(UnloadResult { success: true, error: None })
} else {
log::warn!("No server with PID '{}' found", pid);
Ok(UnloadResult { success: true, error: None })
}
} }
// crypto // crypto
#[tauri::command] #[tauri::command]
pub fn generate_api_key(model_id: String, api_secret: String) -> Result<String, String> { pub fn generate_api_key(model_id: String, api_secret: String) -> Result<String, String> {

View File

@ -1,4 +1,5 @@
mod core; mod core;
use reqwest::Client;
use core::{ use core::{
cmd::get_jan_data_folder_path, cmd::get_jan_data_folder_path,
setup::{self, setup_mcp}, setup::{self, setup_mcp},