From cbfcbea96af5119ec4eff830f3635469e3435bce Mon Sep 17 00:00:00 2001 From: LyAhn Date: Fri, 12 Jun 2026 10:36:05 +0100 Subject: [PATCH] perf(metadata): idle-only backoff and per-item commits The metadata worker now sleeps its 250ms backoff only when the queue is empty or a batch errored, claiming the next batch immediately while work is pending. Each ffprobe result is committed and emitted individually (in its own transaction) instead of after the whole batch, so video metadata progress moves steadily rather than stalling for up to 16 sequential probes. --- src-tauri/src/indexer.rs | 92 +++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/src-tauri/src/indexer.rs b/src-tauri/src/indexer.rs index 1b5fc15..f34ddd0 100644 --- a/src-tauri/src/indexer.rs +++ b/src-tauri/src/indexer.rs @@ -219,10 +219,16 @@ pub fn start_thumbnail_worker( pub fn start_metadata_worker(app: AppHandle, pool: DbPool, media_tools: MediaTools) { std::thread::spawn(move || loop { - if let Err(error) = process_metadata_batch(&app, &pool, &media_tools) { - eprintln!("Metadata worker error: {}", error); + // Only back off when the queue is empty (or errored); while jobs are + // pending, claim the next batch immediately. + match process_metadata_batch(&app, &pool, &media_tools) { + Ok(true) => {} + Ok(false) => std::thread::sleep(std::time::Duration::from_millis(250)), + Err(error) => { + eprintln!("Metadata worker error: {}", error); + std::thread::sleep(std::time::Duration::from_millis(250)); + } } - std::thread::sleep(std::time::Duration::from_millis(250)); }); } @@ -689,7 +695,13 @@ fn persist_thumbnail_results( Ok(()) } -fn process_metadata_batch(app: &AppHandle, pool: &DbPool, media_tools: &MediaTools) -> Result<()> { +/// Returns `Ok(true)` if a batch was claimed and processed, `Ok(false)` if +/// the queue was empty. +fn process_metadata_batch( + app: &AppHandle, + pool: &DbPool, + media_tools: &MediaTools, +) -> Result { let jobs = { with_db_write_lock(|| { let mut conn = pool.get()?; @@ -708,65 +720,49 @@ fn process_metadata_batch(app: &AppHandle, pool: &DbPool, media_tools: &MediaToo }; if jobs.is_empty() { - return Ok(()); + return Ok(false); } - let results = jobs - .into_iter() - .map(|job| { - ( - job.image_id, - probe_video_metadata(media_tools, Path::new(&job.path)), - ) - }) - .collect::>(); + // Probes run sequentially (each ffprobe blocks on its process); results + // are committed per item so progress keeps moving through slow stretches. + for job in jobs { + let metadata_result = probe_video_metadata(media_tools, Path::new(&job.path)); - let updated_images = { - with_db_write_lock(|| { + let updated_image = with_db_write_lock(|| { let mut conn = pool.get()?; let tx = conn.transaction()?; - let mut updated_images = Vec::new(); - - for (image_id, metadata_result) in results { - let metadata = match metadata_result { - Ok(metadata) => metadata, - Err(error) => { - db::mark_metadata_failed(&tx, image_id, &error.to_string())?; - continue; - } - }; - - updated_images.push(db::mark_metadata_ready( + let updated = match metadata_result { + Ok(metadata) => Some(db::mark_metadata_ready( &tx, - image_id, + job.image_id, metadata.duration_ms, metadata.width, metadata.height, metadata.video_codec.as_deref(), metadata.audio_codec.as_deref(), - )?); - } - + )?), + Err(error) => { + db::mark_metadata_failed(&tx, job.image_id, &error.to_string())?; + None + } + }; tx.commit()?; - Ok(updated_images) - })? - }; + Ok(updated) + })?; - if !updated_images.is_empty() { - let folder_ids = updated_images - .iter() - .map(|image| image.folder_id) - .collect::>(); - emit_media_updates( - app, - &MediaUpdateBatch { - images: updated_images, - }, - ); - emit_folder_job_progress(app, pool, &folder_ids.into_iter().collect::>(), true); + if let Some(image) = updated_image { + let folder_id = image.folder_id; + emit_media_updates( + app, + &MediaUpdateBatch { + images: vec![image], + }, + ); + emit_folder_job_progress(app, pool, &[folder_id], false); + } } - Ok(()) + Ok(true) } /// Returns `Ok(true)` if a batch was claimed and processed, `Ok(false)` if