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:
@@ -1,3 +1,4 @@
|
||||
use crate::utils::lock::MutexSafe;
|
||||
use log::{debug, error, info, warn};
|
||||
use super::backend::{PlayerBackend, PlayerError};
|
||||
use super::events::{PlayerEventEmitter, PlayerStatusEvent};
|
||||
@@ -190,7 +191,7 @@ impl MpvBackend {
|
||||
libmpv::events::Event::PlaybackRestart => {
|
||||
debug!("[MpvBackend] Playback started/resumed");
|
||||
|
||||
let media_id = state.lock().unwrap().current_media.as_ref().map(|m| m.id.clone());
|
||||
let media_id = state.lock_safe().current_media.as_ref().map(|m| m.id.clone());
|
||||
|
||||
if let Some(emitter) = &event_emitter {
|
||||
emitter.emit(PlayerStatusEvent::StateChanged {
|
||||
@@ -202,7 +203,7 @@ impl MpvBackend {
|
||||
libmpv::events::Event::PropertyChange { name, .. } if name == "pause" => {
|
||||
// Handle pause state changes
|
||||
if let Ok(is_paused) = mpv.get_property::<bool>("pause") {
|
||||
let media_id = state.lock().unwrap().current_media.as_ref().map(|m| m.id.clone());
|
||||
let media_id = state.lock_safe().current_media.as_ref().map(|m| m.id.clone());
|
||||
|
||||
if let Some(emitter) = &event_emitter {
|
||||
emitter.emit(PlayerStatusEvent::StateChanged {
|
||||
@@ -308,7 +309,7 @@ impl MpvBackend {
|
||||
if !is_paused {
|
||||
// Throttled progress reporting (every 30s)
|
||||
let jellyfin_id = {
|
||||
let state = state_for_position.lock().unwrap();
|
||||
let state = state_for_position.lock_safe();
|
||||
state.current_media.as_ref()
|
||||
.and_then(|m| m.jellyfin_id().map(|s| s.to_string()))
|
||||
};
|
||||
@@ -376,7 +377,7 @@ impl PlayerBackend for MpvBackend {
|
||||
|
||||
// Update state
|
||||
{
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let mut state = self.state.lock_safe();
|
||||
state.current_media = Some(media.clone());
|
||||
}
|
||||
|
||||
@@ -422,7 +423,7 @@ impl PlayerBackend for MpvBackend {
|
||||
message: format!("Failed to stop: {:?}", e),
|
||||
})?;
|
||||
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let mut state = self.state.lock_safe();
|
||||
state.current_media = None;
|
||||
|
||||
Ok(())
|
||||
@@ -460,7 +461,7 @@ impl PlayerBackend for MpvBackend {
|
||||
message: format!("Failed to set volume: {:?}", e),
|
||||
})?;
|
||||
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let mut state = self.state.lock_safe();
|
||||
state.volume = clamped;
|
||||
|
||||
Ok(())
|
||||
@@ -480,7 +481,7 @@ impl PlayerBackend for MpvBackend {
|
||||
}
|
||||
|
||||
fn state(&self) -> PlayerState {
|
||||
let state = self.state.lock().unwrap();
|
||||
let state = self.state.lock_safe();
|
||||
|
||||
if let Some(ref media) = state.current_media {
|
||||
let is_paused = self.mpv.get_property::<bool>("pause").unwrap_or(true);
|
||||
@@ -506,7 +507,7 @@ impl PlayerBackend for MpvBackend {
|
||||
}
|
||||
|
||||
fn volume(&self) -> f32 {
|
||||
let state = self.state.lock().unwrap();
|
||||
let state = self.state.lock_safe();
|
||||
state.volume
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user