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:
@@ -7,6 +7,7 @@ pub mod db_service;
|
||||
pub mod models;
|
||||
pub mod schema;
|
||||
|
||||
use crate::utils::lock::MutexSafe;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -77,7 +78,7 @@ impl Database {
|
||||
/// Run all pending migrations
|
||||
pub fn migrate(&self) -> SqliteResult<()> {
|
||||
info!("Starting database migrations...");
|
||||
let conn = self.conn.lock().unwrap();
|
||||
let conn = self.conn.lock_safe();
|
||||
|
||||
// Create migrations table if it doesn't exist
|
||||
debug!("Creating _migrations table if it doesn't exist...");
|
||||
@@ -172,7 +173,7 @@ mod tests {
|
||||
fn test_migrations_run() {
|
||||
let db = Database::open_in_memory().unwrap();
|
||||
let conn = db.connection();
|
||||
let conn = conn.lock().unwrap();
|
||||
let conn = conn.lock_safe();
|
||||
|
||||
// Check that tables exist
|
||||
let mut stmt = conn
|
||||
@@ -186,7 +187,7 @@ mod tests {
|
||||
fn test_all_tables_created() {
|
||||
let db = Database::open_in_memory().unwrap();
|
||||
let conn = db.connection();
|
||||
let conn = conn.lock().unwrap();
|
||||
let conn = conn.lock_safe();
|
||||
|
||||
let expected_tables = [
|
||||
"servers",
|
||||
@@ -218,7 +219,7 @@ mod tests {
|
||||
fn test_fts_table_created() {
|
||||
let db = Database::open_in_memory().unwrap();
|
||||
let conn = db.connection();
|
||||
let conn = conn.lock().unwrap();
|
||||
let conn = conn.lock_safe();
|
||||
|
||||
let exists: Option<String> = conn
|
||||
.query_row(
|
||||
@@ -234,7 +235,7 @@ mod tests {
|
||||
fn test_server_crud() {
|
||||
let db = Database::open_in_memory().unwrap();
|
||||
let conn = db.connection();
|
||||
let conn = conn.lock().unwrap();
|
||||
let conn = conn.lock_safe();
|
||||
|
||||
// Insert a server
|
||||
conn.execute(
|
||||
@@ -282,7 +283,7 @@ mod tests {
|
||||
fn test_user_crud() {
|
||||
let db = Database::open_in_memory().unwrap();
|
||||
let conn = db.connection();
|
||||
let conn = conn.lock().unwrap();
|
||||
let conn = conn.lock_safe();
|
||||
|
||||
// Create a server first (foreign key)
|
||||
conn.execute(
|
||||
@@ -329,7 +330,7 @@ mod tests {
|
||||
fn test_cascade_delete_server_removes_users() {
|
||||
let db = Database::open_in_memory().unwrap();
|
||||
let conn = db.connection();
|
||||
let conn = conn.lock().unwrap();
|
||||
let conn = conn.lock_safe();
|
||||
|
||||
// Create server and user
|
||||
conn.execute(
|
||||
@@ -367,7 +368,7 @@ mod tests {
|
||||
fn test_item_insert_and_fts_search() {
|
||||
let db = Database::open_in_memory().unwrap();
|
||||
let conn = db.connection();
|
||||
let conn = conn.lock().unwrap();
|
||||
let conn = conn.lock_safe();
|
||||
|
||||
// Create server first
|
||||
conn.execute(
|
||||
@@ -424,7 +425,7 @@ mod tests {
|
||||
fn test_user_data_playback_position() {
|
||||
let db = Database::open_in_memory().unwrap();
|
||||
let conn = db.connection();
|
||||
let conn = conn.lock().unwrap();
|
||||
let conn = conn.lock_safe();
|
||||
|
||||
// Setup: server, user, item
|
||||
conn.execute(
|
||||
@@ -487,7 +488,7 @@ mod tests {
|
||||
fn test_sync_queue_operations() {
|
||||
let db = Database::open_in_memory().unwrap();
|
||||
let conn = db.connection();
|
||||
let conn = conn.lock().unwrap();
|
||||
let conn = conn.lock_safe();
|
||||
|
||||
// Setup
|
||||
conn.execute(
|
||||
@@ -552,7 +553,7 @@ mod tests {
|
||||
fn test_downloads_table() {
|
||||
let db = Database::open_in_memory().unwrap();
|
||||
let conn = db.connection();
|
||||
let conn = conn.lock().unwrap();
|
||||
let conn = conn.lock_safe();
|
||||
|
||||
// Setup
|
||||
conn.execute(
|
||||
@@ -627,7 +628,7 @@ mod tests {
|
||||
|
||||
// Tables should still exist
|
||||
let conn = db.connection();
|
||||
let conn = conn.lock().unwrap();
|
||||
let conn = conn.lock_safe();
|
||||
|
||||
let count: i32 = conn
|
||||
.query_row(
|
||||
@@ -643,7 +644,7 @@ mod tests {
|
||||
fn test_global_active_user_deactivation() {
|
||||
let db = Database::open_in_memory().unwrap();
|
||||
let conn = db.connection();
|
||||
let conn = conn.lock().unwrap();
|
||||
let conn = conn.lock_safe();
|
||||
|
||||
// Create two servers
|
||||
conn.execute(
|
||||
@@ -713,7 +714,7 @@ mod tests {
|
||||
fn test_active_session_query_ordering() {
|
||||
let db = Database::open_in_memory().unwrap();
|
||||
let conn = db.connection();
|
||||
let conn = conn.lock().unwrap();
|
||||
let conn = conn.lock_safe();
|
||||
|
||||
// Create server
|
||||
conn.execute(
|
||||
|
||||
Reference in New Issue
Block a user