Frontend performance: - Debounce DataGrid search (150ms) and replace O(n) row wrapping with index array - Replace QueryStore deep watcher with targeted field-level persistence snapshot - Extract shared nodeConfig computed in TreeItem (eliminates 15x redundant getConfig calls) - Clear tab result data on close to free memory - Add LRU eviction for completion caches (50 entry cap) - Replace savedSql version string join with numeric counter Memory leak fixes: - Store Tauri event unlisten handles and clean up on unmount - Clean up DataGrid search timer on unmount Backend security & resource management: - Add 50MB request body size limit - Reject file uploads >100MB and SQL files >200MB - Clean up SSE channels after transfer/import completes - Recover from poisoned mutex instead of panicking - Delete temporary upload files after preview - Validate SQL file paths to prevent directory traversal
81 lines
2.1 KiB
Rust
81 lines
2.1 KiB
Rust
use std::collections::HashMap;
|
|
use std::sync::{Arc, Mutex};
|
|
use tokio_util::sync::CancellationToken;
|
|
|
|
#[derive(Clone, Default)]
|
|
pub struct RunningQueries {
|
|
inner: Arc<Mutex<HashMap<String, CancellationToken>>>,
|
|
}
|
|
|
|
impl RunningQueries {
|
|
pub fn register(&self, execution_id: String) -> RegisteredQuery {
|
|
let token = CancellationToken::new();
|
|
self.inner.lock().unwrap_or_else(|e| e.into_inner()).insert(execution_id.clone(), token.clone());
|
|
|
|
RegisteredQuery { execution_id, token, running_queries: self.clone() }
|
|
}
|
|
|
|
pub fn cancel(&self, execution_id: &str) -> bool {
|
|
let token = self.inner.lock().unwrap_or_else(|e| e.into_inner()).get(execution_id).cloned();
|
|
|
|
if let Some(token) = token {
|
|
token.cancel();
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn has(&self, execution_id: &str) -> bool {
|
|
self.inner.lock().unwrap_or_else(|e| e.into_inner()).contains_key(execution_id)
|
|
}
|
|
|
|
fn remove(&self, execution_id: &str) {
|
|
self.inner.lock().unwrap_or_else(|e| e.into_inner()).remove(execution_id);
|
|
}
|
|
}
|
|
|
|
pub struct RegisteredQuery {
|
|
execution_id: String,
|
|
token: CancellationToken,
|
|
running_queries: RunningQueries,
|
|
}
|
|
|
|
impl RegisteredQuery {
|
|
pub fn token(&self) -> CancellationToken {
|
|
self.token.clone()
|
|
}
|
|
}
|
|
|
|
impl Drop for RegisteredQuery {
|
|
fn drop(&mut self) {
|
|
self.running_queries.remove(&self.execution_id);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::RunningQueries;
|
|
|
|
#[test]
|
|
fn cancel_marks_registered_query_as_cancelled() {
|
|
let running = RunningQueries::default();
|
|
let registered = running.register("exec-1".to_string());
|
|
|
|
assert!(running.cancel("exec-1"));
|
|
assert!(registered.token().is_cancelled());
|
|
}
|
|
|
|
#[test]
|
|
fn dropping_registration_removes_running_query() {
|
|
let running = RunningQueries::default();
|
|
let registered = running.register("exec-1".to_string());
|
|
|
|
assert!(running.has("exec-1"));
|
|
drop(registered);
|
|
|
|
assert!(!running.has("exec-1"));
|
|
}
|
|
}
|