Architecture remediation A/B/F: poison-tolerant locks, graceful backend init, doc fixes
Workstream A — poison-tolerant locking: - Add utils/lock.rs with MutexSafe/RwLockSafe extension traits that recover a poisoned std::sync lock instead of panicking, plus unit tests. - Replace all 153 .lock().unwrap() and 4 .read()/.write().unwrap() production sites with _safe variants across 14 files, eliminating the player crash-cascade class. Tokio async mutexes are unchanged. Workstream B — graceful backend init: - create_player_backend no longer panics when MPV/ExoPlayer fail to initialize; it falls back to NullBackend and emits a backend-init-failed event so the UI can show "playback unavailable" instead of the app crashing. Fatal DB-setup panics are kept. Workstream F — doc reconciliation: - Rewrite software-architecture.md's inaccurate "thin UI / ~800 lines" claims to reflect reality (~20.5k non-test frontend) and document the events+polling hybrid plus the new locking/backend-init behavior.
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
// @req: DR-012 - Local database for media metadata cache
|
||||
// @req: DR-013 - Repository pattern for online/offline data access
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::utils::lock::MutexSafe;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
@@ -546,16 +548,16 @@ mod tests {
|
||||
}
|
||||
|
||||
fn get_query_count(&self) -> usize {
|
||||
*self.query_count.lock().unwrap()
|
||||
*self.query_count.lock_safe()
|
||||
}
|
||||
|
||||
fn get_save_count(&self) -> usize {
|
||||
*self.save_count.lock().unwrap()
|
||||
*self.save_count.lock_safe()
|
||||
}
|
||||
|
||||
async fn save_to_cache(&self, _parent_id: &str, items: &[MediaItem]) -> Result<usize, RepoError> {
|
||||
*self.save_count.lock().unwrap() += 1;
|
||||
*self.items.lock().unwrap() = items.to_vec();
|
||||
*self.save_count.lock_safe() += 1;
|
||||
*self.items.lock_safe() = items.to_vec();
|
||||
Ok(items.len())
|
||||
}
|
||||
}
|
||||
@@ -567,8 +569,8 @@ mod tests {
|
||||
}
|
||||
|
||||
async fn get_items(&self, _parent_id: &str, _options: Option<GetItemsOptions>) -> Result<SearchResult, RepoError> {
|
||||
*self.query_count.lock().unwrap() += 1;
|
||||
let items = self.items.lock().unwrap().clone();
|
||||
*self.query_count.lock_safe() += 1;
|
||||
let items = self.items.lock_safe().clone();
|
||||
let count = items.len();
|
||||
Ok(SearchResult {
|
||||
items,
|
||||
@@ -715,7 +717,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn get_query_count(&self) -> usize {
|
||||
*self.query_count.lock().unwrap()
|
||||
*self.query_count.lock_safe()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,7 +728,7 @@ mod tests {
|
||||
}
|
||||
|
||||
async fn get_items(&self, _parent_id: &str, _options: Option<GetItemsOptions>) -> Result<SearchResult, RepoError> {
|
||||
*self.query_count.lock().unwrap() += 1;
|
||||
*self.query_count.lock_safe() += 1;
|
||||
Ok(SearchResult {
|
||||
items: self.items.clone(),
|
||||
total_record_count: self.items.len(),
|
||||
|
||||
Reference in New Issue
Block a user