perf: separate library DB connection for download post-processing
Add a second LibraryDatabase connection to OfflineCacheState dedicated to download post-processing writes. SQLite WAL mode natively handles concurrent reads (UI via main connection) and writes (downloads via this separate connection), eliminating app-level mutex contention that caused progressive UI lag during bulk downloads. Init after library.init_at() in both activate_session and activate_offline_session. Teardown before main offline_cache teardown.
This commit is contained in:
1 parent
644571f82a
commit
76cf401a3a
3 files changed
+26
-4
No files matched your search
@@ -6549,7 +6549,6 @@ pub async fn v2_cache_track_for_offline(
|
||||
sample_rate: Option<f64>,
|
||||
state: State<'_, AppState>,
|
||||
cache_state: State<'_, OfflineCacheState>,
|
||||
library_state: State<'_, crate::library::LibraryState>,
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> Result<(), String> {
|
||||
log::info!("Command: v2_cache_track_for_offline {} - {} by {}", track_id, title, artist);
|
||||
@@ -6581,7 +6580,7 @@ pub async fn v2_cache_track_for_offline(
|
||||
cache_state.fetcher.clone(),
|
||||
cache_state.db.clone(),
|
||||
cache_state.get_cache_path(),
|
||||
library_state.db.clone(),
|
||||
cache_state.library_db.clone(),
|
||||
app_handle.clone(),
|
||||
cache_state.cache_semaphore.clone(),
|
||||
);
|
||||
@@ -6608,7 +6607,6 @@ pub async fn v2_cache_tracks_batch_for_offline(
|
||||
tracks: Vec<BatchTrackInfo>,
|
||||
state: State<'_, AppState>,
|
||||
cache_state: State<'_, OfflineCacheState>,
|
||||
library_state: State<'_, crate::library::LibraryState>,
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> Result<(), String> {
|
||||
log::info!("Command: v2_cache_tracks_batch_for_offline ({} tracks)", tracks.len());
|
||||
@@ -6652,7 +6650,7 @@ pub async fn v2_cache_tracks_batch_for_offline(
|
||||
cache_state.fetcher.clone(),
|
||||
cache_state.db.clone(),
|
||||
cache_state.get_cache_path(),
|
||||
library_state.db.clone(),
|
||||
cache_state.library_db.clone(),
|
||||
app_handle.clone(),
|
||||
cache_state.cache_semaphore.clone(),
|
||||
);
|
||||
|
||||
@@ -135,6 +135,9 @@ pub struct OfflineCacheState {
|
||||
/// Cache limit in bytes (None = unlimited)
|
||||
pub limit_bytes: Arc<Mutex<Option<u64>>>,
|
||||
pub cache_semaphore: Arc<Semaphore>,
|
||||
/// Separate library DB connection for download post-processing writes.
|
||||
/// This avoids contending with the main library DB mutex used by UI queries.
|
||||
pub library_db: Arc<Mutex<Option<qbz_library::LibraryDatabase>>>,
|
||||
}
|
||||
|
||||
impl OfflineCacheState {
|
||||
@@ -165,6 +168,7 @@ impl OfflineCacheState {
|
||||
cache_dir: Arc::new(RwLock::new(cache_dir.clone())),
|
||||
limit_bytes: Arc::new(Mutex::new(default_limit)),
|
||||
cache_semaphore: Arc::new(Semaphore::new(3)),
|
||||
library_db: Arc::new(Mutex::new(None)),
|
||||
};
|
||||
|
||||
log::info!("Offline cache initialized at: {:?}", cache_dir);
|
||||
@@ -183,6 +187,7 @@ impl OfflineCacheState {
|
||||
cache_dir: Arc::new(RwLock::new(cache_dir)),
|
||||
limit_bytes: Arc::new(Mutex::new(Some(2 * 1024 * 1024 * 1024u64))),
|
||||
cache_semaphore: Arc::new(Semaphore::new(3)),
|
||||
library_db: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +211,24 @@ impl OfflineCacheState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Open a separate library DB connection for download post-processing.
|
||||
/// Must be called after library.init_at() so the schema exists.
|
||||
pub async fn init_library_connection(&self, data_dir: &std::path::Path) -> Result<(), String> {
|
||||
let db_path = data_dir.join("library.db");
|
||||
let lib_db = qbz_library::LibraryDatabase::open(&db_path)
|
||||
.map_err(|e| format!("Failed to open download library connection: {}", e))?;
|
||||
let mut guard = self.library_db.lock().await;
|
||||
*guard = Some(lib_db);
|
||||
log::info!("Offline cache: separate library DB connection opened at {:?}", db_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn teardown(&self) {
|
||||
// Close library connection first (before main teardown)
|
||||
{
|
||||
let mut lib_guard = self.library_db.lock().await;
|
||||
*lib_guard = None;
|
||||
}
|
||||
let mut guard = self.db.lock().await;
|
||||
*guard = None;
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ pub async fn activate_session(app: &tauri::AppHandle, user_id: u64) -> Result<()
|
||||
|
||||
// Cache-dir stores
|
||||
offline_cache.init_at(&cache_dir).await?;
|
||||
offline_cache.init_library_connection(&data_dir).await?;
|
||||
lyrics.init_at(&cache_dir).await?;
|
||||
|
||||
// Run deferred subscription purge check
|
||||
@@ -313,6 +314,7 @@ pub async fn activate_offline_session(app: &tauri::AppHandle) -> Result<(), Stri
|
||||
library.init_at(&data_dir).await?;
|
||||
offline.init_at(&data_dir)?;
|
||||
offline_cache.init_at(&cache_dir).await?;
|
||||
offline_cache.init_library_connection(&data_dir).await?;
|
||||
audio_settings.init_at(&data_dir)?;
|
||||
playback_prefs.init_at(&data_dir)?;
|
||||
|
||||
|
||||
Reference in new issue
Block a user