From af7ca2ef0b44bd269bb818ab6fcf2018acab6239 Mon Sep 17 00:00:00 2001 From: Teodor-Alexandru Dicu <92853884+DTeodor-Alexaandru@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:41:13 +0300 Subject: [PATCH] add error handling where needed --- Cargo.toml | 8 ++ .../src/backend/core/connection_manager.rs | 3 +- src-tauri/src/backend/core/mod.rs | 12 +- src-tauri/src/backend/core/state.rs | 125 +++++++++++++++--- src-tauri/src/backend/domain/application.rs | 5 +- src-tauri/src/backend/domain/async_op.rs | 2 +- src-tauri/src/backend/mappers/async_ops.rs | 12 +- src-tauri/src/features/applications.rs | 16 ++- src-tauri/src/main.rs | 3 +- src-tauri/src/utils/common.rs | 125 ++++-------------- src-tauri/src/utils/error.rs | 19 +++ src/main.rs | 3 - 12 files changed, 187 insertions(+), 146 deletions(-) create mode 100644 Cargo.toml delete mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3fb54ca --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "async-debugger" +version = "0.1.0" +edition = "2024" + +[dependencies] +tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time", "tracing", "net" ] } +console-subscriber = "0.4.1" \ No newline at end of file diff --git a/src-tauri/src/backend/core/connection_manager.rs b/src-tauri/src/backend/core/connection_manager.rs index 2ed5e10..6afab13 100644 --- a/src-tauri/src/backend/core/connection_manager.rs +++ b/src-tauri/src/backend/core/connection_manager.rs @@ -247,7 +247,7 @@ impl ConnectionManager { Self::check_app_stats(&mut sys, pid).await } { updates_sender.send((cloned_id, Event::ApplicationUpdated(app_update))).await.ok(); - } else if let Some(new_pid) = get_pid_hosting_at(url.clone()){ + } else if let Ok(new_pid) = get_pid_hosting_at(url.clone()){ if new_pid != pid { pid = new_pid; updates_sender.send((cloned_id, Event::PidChanged(new_pid))).await.ok(); @@ -334,7 +334,6 @@ impl ConnectionManager { /// Returns `None` if the process no longer exists. async fn check_app_stats(sys: &mut System, pid: u32) -> Option { let cpu_count = sys.cpus().len() as f32; - // println!("CPUS: {cpu_count}"); let process = sys.process(Pid::from_u32(pid))?; let cpu_per_core = process.cpu_usage() / cpu_count; let memory_mb = process.memory() / 1000000; diff --git a/src-tauri/src/backend/core/mod.rs b/src-tauri/src/backend/core/mod.rs index 7479c7c..fab9d16 100644 --- a/src-tauri/src/backend/core/mod.rs +++ b/src-tauri/src/backend/core/mod.rs @@ -148,7 +148,7 @@ impl StateManager { }; tokio::join!(task_future, resource_future, async_op_future); - println!("{:?}", warnings); + info!("{:?}", warnings); }, Event::ApplicationUpdated(update) => { @@ -156,28 +156,28 @@ impl StateManager { }, Event::Connecting => { - println!("Connecting.."); + info!("Connecting.."); self.state.handle_app_conn_update(app_id, ConnectionStatus::Connecting).await; }, Event::Connected => { - println!("Connected"); + info!("Connected"); self.state.handle_app_conn_update(app_id, ConnectionStatus::Connected).await; }, Event::Disconnected => { - println!("Disconnected app"); + info!("Disconnected app"); self.delete_connection(app_id).await; self.state.handle_app_conn_update(app_id, ConnectionStatus::Disconnected).await; }, Event::Error(err) => { - println!("Error with app connection: {err:?}"); + info!("Error with app connection: {err:?}"); self.state.handle_app_conn_update(app_id, ConnectionStatus::Error(err.to_string())).await; } Event::PidChanged(new_pid) => { - println!("PID changed to {new_pid}"); + info!("PID changed to {new_pid}"); self.state.handle_pid_changed(app_id, new_pid).await; } } diff --git a/src-tauri/src/backend/core/state.rs b/src-tauri/src/backend/core/state.rs index 5a20b06..dfd528f 100644 --- a/src-tauri/src/backend/core/state.rs +++ b/src-tauri/src/backend/core/state.rs @@ -3,13 +3,14 @@ use super::database::Database; use crate::backend::core::warnings::TaskWarnings; use crate::backend::domain::application::{ApplicationState, ConnectionStatus}; use crate::backend::domain::async_op::{CPUOverview, TaskOp}; +use crate::backend::domain::has_app_name::HasAppName; use crate::backend::domain::resource::ResourceStatus; use crate::backend::domain::TaskState; -use crate::backend::infra::guard::DataBaseWrite; +use crate::backend::infra::guard::{DataBaseWrite, WriteableDataBaseGuard}; use crate::backend::infra::storage::Storage; -use crate::utils::common::{ - get_pid_hosting_at, rename_database_keys, rename_database_keys_and_app_name, -}; +use crate::utils::common::get_pid_hosting_at; +use std::fmt::Debug; + use crate::utils::error::Error as TraceError; use crate::{ backend::domain::{application::Application, poll::Poll, resource::Resource, Task}, @@ -23,6 +24,8 @@ use console_api::async_ops::AsyncOpUpdate; use console_api::resources::ResourceUpdate; use console_api::tasks::TaskUpdate; use log::{debug, error, info, warn}; +use serde::Serialize; +use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; @@ -91,7 +94,7 @@ impl State { // Refresh PIDs for applications whose host process may have changed let mut guard = database.applications_write().await; for (_uuid, app) in guard.iter_mut() { - if let Some(pid) = get_pid_hosting_at(app.url().clone()) { + if let Ok(pid) = get_pid_hosting_at(app.url().clone()) { if app.pid() != pid { debug!("Updating the PID for app {} to {}", app.title(), pid); app.writeable().set_pid(pid); @@ -211,12 +214,12 @@ impl State { pub async fn edit_app(&self, new_title: String, old_title: String) -> Result<(), TraceError> { { let mut async_guard = self.database.async_ops_write().await; - rename_database_keys(&mut async_guard, new_title.clone(), old_title.clone()); + Self::rename_database_keys(&mut async_guard, new_title.clone(), old_title.clone()); } { let mut resource_guard = self.database.resources_write().await; - rename_database_keys_and_app_name( + Self::rename_database_keys_and_app_name( &mut resource_guard, new_title.clone(), old_title.clone(), @@ -225,7 +228,7 @@ impl State { { let mut tasks_guard = self.database.tasks_write().await; - rename_database_keys_and_app_name( + Self::rename_database_keys_and_app_name( &mut tasks_guard, new_title.clone(), old_title.clone(), @@ -234,7 +237,7 @@ impl State { { let mut tasks_ops_guard = self.database.tasks_ops_write().await; - rename_database_keys(&mut tasks_ops_guard, new_title.clone(), old_title.clone()); + Self::rename_database_keys(&mut tasks_ops_guard, new_title.clone(), old_title.clone()); } { @@ -291,7 +294,7 @@ impl State { // debug for missed task_updates if task_update.dropped_events > 0 { - println!("missed task updates: {:?}", task_update.dropped_events); + info!("missed task updates: {:?}", task_update.dropped_events); } if let Some(app) = self.database.applications_read().await.get(&app_id) { @@ -556,7 +559,6 @@ impl State { if let Some(app) = apps.get_mut(&app_id) { app.writeable().set_pid(new_pid); } - // drop guard } /// Lookup the current PID for an application. @@ -669,7 +671,7 @@ impl State { ) { // debug for missed resources_updates if resources_update.dropped_events > 0 { - println!( + info!( "missed resources updates: {:?}", resources_update.dropped_events ); @@ -887,7 +889,7 @@ impl State { /// task metadata (name/color) and the new CPU overview. pub async fn handle_async_op_update(&self, app_id: Uuid, async_op_update: AsyncOpUpdate) { if async_op_update.dropped_events > 0 { - println!( + warn!( "missed async_op updates: {:?}", async_op_update.dropped_events ); @@ -920,15 +922,18 @@ impl State { // Insert any new async-ops for raw in async_op_update.new_async_ops { - if let Some(mut domain_async_op) = map_to_domain_async_op(&raw) { - let key = format!("{}.{}", app.title(), domain_async_op.resource_id); - if let Some(resource) = self.database.resources_read().await.get(&key) { - domain_async_op.resource_target = resource.target.clone(); - self.database.async_ops_write().await.insert( - format!("{}.{}", app.title(), domain_async_op.id), - Arc::new(domain_async_op), - ); + match map_to_domain_async_op(&raw) { + Ok(mut domain_async_op) => { + let key = format!("{}.{}", app.title(), domain_async_op.resource_id); + if let Some(resource) = self.database.resources_read().await.get(&key) { + domain_async_op.resource_target = resource.target.clone(); + self.database.async_ops_write().await.insert( + format!("{}.{}", app.title(), domain_async_op.id), + Arc::new(domain_async_op), + ); + } } + Err(err) => error!("Error at inserting new async-ops, error: {err:?}"), } } @@ -1110,6 +1115,84 @@ impl State { } // endregion + + /// This is used when the user renames an application + /// + /// Renames keys in the given database guard by replacing the `old_title` prefix + /// in keys with the `new_title` prefix. The function iterates over all keys, + /// collects the keys that start with `old_title.`, and renames them accordiungly. + /// + /// # Arguments + /// + /// * `guard` - A mutable reference to a writable database guard holding a `HashMap` + /// where keys are strings and values are of generic type `T`. + /// * `new_title` - The new title string to replace the old title prefix in keys. + /// * `old_title` - The old title string prefix to be replaced in keys. + /// + /// # Type Parameters + /// + /// * `T` - The type of the values in the hashmap. Must implement `Serialize` and `Debug`. + fn rename_database_keys( + guard: &mut WriteableDataBaseGuard<'_, HashMap>, + new_title: String, + old_title: String, + ) { + let mut changes = Vec::new(); + for key in guard.keys() { + if let Some(rest) = key.strip_prefix(&format!("{}.", old_title)) { + let new_key = format!("{}.{}", new_title, rest); + changes.push((key.clone(), new_key)); + } + } + + for (old, new) in changes { + if let Some(val) = guard.remove(&old) { + guard.insert(new, val); + } + } + } + + /// This is used when the user renames an application + /// + /// Renames keys in the given database guard by replacing the `old_title` prefix + /// in keys with the `new_title` prefix. In addition, it updates the `app_name` + /// attribute of the value associated with each renamed key. + /// + /// This function works on database guards containing `HashMap>`, + /// where `T` must implement `Serialize`, `Debug`, `HasAppName`, and `Clone`. + /// + /// # Arguments + /// + /// * `guard` - A mutable reference to a writable database guard holding a `HashMap` + /// where keys are strings and values are `Arc` wrapped generic type `T`. + /// * `new_title` - The new title string to replace the old title prefix in keys. + /// * `old_title` - The old title string prefix to be replaced in keys. + /// + /// # Type Parameters + /// + /// * `T` - The type of the values inside the Arc in the hashmap. Must implement + /// Implements `Serialize`, `Debug`, `HasAppName` (a trait providing `set_app_name`), and `Clone`. + fn rename_database_keys_and_app_name( + guard: &mut WriteableDataBaseGuard<'_, HashMap>>, + new_title: String, + old_title: String, + ) { + let mut changes = Vec::new(); + for key in guard.keys() { + if let Some(rest) = key.strip_prefix(&format!("{}.", old_title)) { + let new_key = format!("{}.{}", new_title, rest); + changes.push((key.clone(), new_key)); + } + } + + for (old, new) in changes { + if let Some(mut val_arc) = guard.remove(&old) { + let val = Arc::make_mut(&mut val_arc); + val.set_app_name(new_title.clone()); + guard.insert(new, Arc::new(val.clone())); + } + } + } } #[cfg(test)] diff --git a/src-tauri/src/backend/domain/application.rs b/src-tauri/src/backend/domain/application.rs index d3c4297..fdf17e7 100644 --- a/src-tauri/src/backend/domain/application.rs +++ b/src-tauri/src/backend/domain/application.rs @@ -66,8 +66,7 @@ impl Application { /// be retrieved. pub fn new(title: String, url: Url) -> Result { // Find the PID of the app - let pid = - get_pid_hosting_at(url.clone()).ok_or(TraceError::PIDNotFound { url: url.clone() })?; + let pid = get_pid_hosting_at(url.clone())?; let start_time = get_process_start_time(pid).ok_or(TraceError::PIDNotFound { url: url.clone() })?; @@ -193,7 +192,7 @@ impl Storable> for Application { #[cfg(test)] mod tests { use super::*; - use crate::core::connection_manager::Command; + use crate::backend::core::connection_manager::Command; use serde_json::to_string_pretty; use std::collections::HashMap; use std::fs; diff --git a/src-tauri/src/backend/domain/async_op.rs b/src-tauri/src/backend/domain/async_op.rs index 28ab3ad..e86bd4d 100644 --- a/src-tauri/src/backend/domain/async_op.rs +++ b/src-tauri/src/backend/domain/async_op.rs @@ -94,6 +94,7 @@ impl Storable> for TaskOp { #[cfg(test)] mod tests { use super::*; + use crate::utils::error::Error as TraceError; use chrono::TimeZone; use serde_json::to_string_pretty; use std::collections::HashMap; @@ -101,7 +102,6 @@ mod tests { use std::io::Write; use tempfile::tempdir; use tokio; - use utils::error::Error as TraceError; #[tokio::test] async fn asyncop_load_all_success() { diff --git a/src-tauri/src/backend/mappers/async_ops.rs b/src-tauri/src/backend/mappers/async_ops.rs index fb4d451..4165ec2 100644 --- a/src-tauri/src/backend/mappers/async_ops.rs +++ b/src-tauri/src/backend/mappers/async_ops.rs @@ -1,18 +1,20 @@ //! Convert from `console_api::async_ops::AsyncOp` to our domain `AsyncOp`. -use crate::backend::domain::async_op::AsyncOp; +use crate::{backend::domain::async_op::AsyncOp, utils::error::Error as TraceError}; use console_api::async_ops; /// Maps a protobuf‐style `console_api` AsyncOp into the domain `AsyncOp`. /// -/// Returns `None` if either `id` or `resource_id` is missing. -pub fn map_to_domain_async_op(async_op: &async_ops::AsyncOp) -> Option { +/// Returns `TraceError` if either `id` or `resource_id` is missing. +pub fn map_to_domain_async_op(async_op: &async_ops::AsyncOp) -> Result { match (async_op.id, async_op.resource_id) { - (Some(id), Some(resource_id)) => Some(AsyncOp { + (Some(id), Some(resource_id)) => Ok(AsyncOp { id: id.id, resource_id: resource_id.id, resource_target: None, }), - _ => None, + (None, Some(_resource_id)) => Err(TraceError::IDNotFound), + (Some(_id), None) => Err(TraceError::ResourceIDNotFound), + (None, None) => Err(TraceError::IDAndResourceIDNotFound), } } diff --git a/src-tauri/src/features/applications.rs b/src-tauri/src/features/applications.rs index b6ea984..c5864ad 100644 --- a/src-tauri/src/features/applications.rs +++ b/src-tauri/src/features/applications.rs @@ -1,6 +1,6 @@ use crate::backend::core::StateManager; use crate::utils::error::Error; -use log::info; +use log::{error, info}; use serde::{Deserialize, Serialize}; use std::path::Path; use std::sync::Arc; @@ -164,11 +164,10 @@ pub async fn get_app_pid( state_manager: TauriState<'_, Arc>, uuid: Uuid, ) -> Result { - state_manager - .state - .get_pid_for(uuid) - .await - .ok_or_else(|| Error::Anyhow(anyhow::anyhow!("App {uuid} not found"))) + state_manager.state.get_pid_for(uuid).await.ok_or_else(|| { + error!("Error at fetching the current PID of the appliction with uuid: {uuid}"); + Error::Anyhow(anyhow::anyhow!("App {uuid} not found")) + }) } #[tauri::command] @@ -183,7 +182,10 @@ pub async fn export_app_instance( .export_app_instance(title, name) .await .map(|p| p.to_string_lossy().to_string()) - .map_err(|e| format!("Export error: {}", e)) + .map_err(|e| { + error!("Export error: {e}"); + format!("Export error: {e}") + }) } #[tauri::command] diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 8f73b7f..56731a5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,11 +1,12 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use env_logger as _; +use log::info; #[tokio::main] async fn main() { env_logger::init(); - println!("Starting"); + info!("Starting"); tokio_display_lib::run().await } diff --git a/src-tauri/src/utils/common.rs b/src-tauri/src/utils/common.rs index e7f5979..473f932 100644 --- a/src-tauri/src/utils/common.rs +++ b/src-tauri/src/utils/common.rs @@ -1,14 +1,11 @@ +use crate::utils::error::Error as TraceError; use chrono::{DateTime, Local}; -use serde::Serialize; -use std::fmt::Debug; -use std::sync::Arc; -use std::{collections::HashMap, process::Command}; +use log::error; +use log::info; +use std::process::Command; use sysinfo::{Pid, System}; use url::Url; -use crate::backend::domain::has_app_name::HasAppName; -use crate::backend::infra::guard::WriteableDataBaseGuard; - /// Returns the PID of the process listening on the given URL’s port, if any. /// /// # Arguments @@ -24,10 +21,13 @@ use crate::backend::infra::guard::WriteableDataBaseGuard; /// /// On Linux and macOS, this spawns `lsof -ti :` and parses its output. /// On Windows, this spawns `netstat -ano` and searches for the port in each line. -pub fn get_pid_hosting_at(url: Url) -> Option { +pub fn get_pid_hosting_at(url: Url) -> Result { // Extract the port number from the URL; return None if absent. - let port = url.port()?; - println!("Port: {}", port); + let port = url + .port() + .ok_or(TraceError::PortNotFound { url: url.clone() })?; + + info!("Port: {}", port); #[cfg(any(target_os = "linux", target_os = "macos"))] { @@ -35,11 +35,14 @@ pub fn get_pid_hosting_at(url: Url) -> Option { let output = Command::new("lsof") .args(["-ti", &format!(":{}", port)]) .output() - .ok()?; + .map_err(|err| { + error!("Cannot execute lsof, error: {err:?}"); + TraceError::PIDNotFound { url: url.clone() } + })?; // If no output, no process found if output.stdout.is_empty() { - return None; + return Err(TraceError::PIDNotFound { url: url.clone() }); } // Parse the PID out of the stdout @@ -48,17 +51,23 @@ pub fn get_pid_hosting_at(url: Url) -> Option { // is typically the last end line-separated token. if let Some(pid_str) = output_str.lines().last() { if let Ok(pid) = pid_str.parse::() { - println!("{:?}", pid); - return Some(pid); + info!("Found pin {:?} for url {:?}", pid, url); + return Ok(pid); } } - None + Err(TraceError::PIDNotFound { url: url.clone() }) } #[cfg(target_os = "windows")] { // Use netstat to list all TCP/UDP connections with PIDs. - let output = Command::new("netstat").args(["-ano"]).output().ok()?; + let output = Command::new("netstat") + .args(["-ano"]) + .output() + .map_err(|err| { + error!("Cannot execute netstat, error: {err:?}"); + TraceError::PIDNotFound { url: url.clone() } + })?; // This needs to be lossy, because the netstat output sometimes contains non UTF-8 characters let stdout = String::from_utf8_lossy(&output.stdout); @@ -68,12 +77,12 @@ pub fn get_pid_hosting_at(url: Url) -> Option { // The PID is typically the last whitespace-separated token. if let Some(pid_str) = line.split_whitespace().last() { if let Ok(pid) = pid_str.parse::() { - return Some(pid); + return Ok(pid); } } } } - None + Err(TraceError::PIDNotFound { url: url.clone() }) } } @@ -104,84 +113,6 @@ pub fn get_process_start_time(pid: u32) -> Option { return Some(date); } - // PID not found in the system + error!("PID not found in the system"); None } - -/// This is used when the user renames an application -/// -/// Renames keys in the given database guard by replacing the `old_title` prefix -/// in keys with the `new_title` prefix. The function iterates over all keys, -/// collects the keys that start with `old_title.`, and renames them accordiungly. -/// -/// # Arguments -/// -/// * `guard` - A mutable reference to a writable database guard holding a `HashMap` -/// where keys are strings and values are of generic type `T`. -/// * `new_title` - The new title string to replace the old title prefix in keys. -/// * `old_title` - The old title string prefix to be replaced in keys. -/// -/// # Type Parameters -/// -/// * `T` - The type of the values in the hashmap. Must implement `Serialize` and `Debug`. -pub fn rename_database_keys( - guard: &mut WriteableDataBaseGuard<'_, HashMap>, - new_title: String, - old_title: String, -) { - let mut changes = Vec::new(); - for key in guard.keys() { - if let Some(rest) = key.strip_prefix(&format!("{}.", old_title)) { - let new_key = format!("{}.{}", new_title, rest); - changes.push((key.clone(), new_key)); - } - } - - for (old, new) in changes { - if let Some(val) = guard.remove(&old) { - guard.insert(new, val); - } - } -} - -/// This is used when the user renames an application -/// -/// Renames keys in the given database guard by replacing the `old_title` prefix -/// in keys with the `new_title` prefix. In addition, it updates the `app_name` -/// attribute of the value associated with each renamed key. -/// -/// This function works on database guards containing `HashMap>`, -/// where `T` must implement `Serialize`, `Debug`, `HasAppName`, and `Clone`. -/// -/// # Arguments -/// -/// * `guard` - A mutable reference to a writable database guard holding a `HashMap` -/// where keys are strings and values are `Arc` wrapped generic type `T`. -/// * `new_title` - The new title string to replace the old title prefix in keys. -/// * `old_title` - The old title string prefix to be replaced in keys. -/// -/// # Type Parameters -/// -/// * `T` - The type of the values inside the Arc in the hashmap. Must implement -/// Implements `Serialize`, `Debug`, `HasAppName` (a trait providing `set_app_name`), and `Clone`. -pub fn rename_database_keys_and_app_name( - guard: &mut WriteableDataBaseGuard<'_, HashMap>>, - new_title: String, - old_title: String, -) { - let mut changes = Vec::new(); - for key in guard.keys() { - if let Some(rest) = key.strip_prefix(&format!("{}.", old_title)) { - let new_key = format!("{}.{}", new_title, rest); - changes.push((key.clone(), new_key)); - } - } - - for (old, new) in changes { - if let Some(mut val_arc) = guard.remove(&old) { - let val = Arc::make_mut(&mut val_arc); - val.set_app_name(new_title.clone()); - guard.insert(new, Arc::new(val.clone())); - } - } -} diff --git a/src-tauri/src/utils/error.rs b/src-tauri/src/utils/error.rs index 390a030..19a6160 100644 --- a/src-tauri/src/utils/error.rs +++ b/src-tauri/src/utils/error.rs @@ -57,6 +57,25 @@ pub enum Error { #[error("Task not found: {0}")] TaskNotFound(String), + + /// Failed to get the port of URL + #[error("PortNotFound: Could not find the port URL: {url}")] + PortNotFound { + /// The application URL for which the port was not found. + url: Url, + }, + + // AsyncOp ID not found + #[error("IDNotFound")] + IDNotFound, + + // AsyncOp ResourceID not found + #[error("ResourceIDNotFound")] + ResourceIDNotFound, + + // AsyncOp IDAndResourceIDNotFound not found + #[error("IDAndResourceIDNotFound")] + IDAndResourceIDNotFound, } impl serde::Serialize for Error { diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -}