Feature/agentic loop fixes #111

Merged
cameron merged 3 commits from feature/agentic-loop-fixes into master 2026-09-24 15:59:59 +00:00
3 changed files with 24 additions and 2 deletions
Showing only changes of commit 23371ad0bc - Show all commits

No files matched your search

+19
View File
@@ -156,6 +156,25 @@ pub fn detect_missing_files_for_library(
(deleted, next_offset) (deleted, next_offset)
} }
/// Re-gather query-planner statistics (`sqlite_stat1`).
///
/// The migrations run `ANALYZE` once, but the stats never refresh after
/// that. A snapshot taken while `image_exif.content_hash` was all-NULL
/// told the planner every hash matches every row, so the correlated
/// `content_hash = ...` subqueries in [`refresh_back_refs`] scanned
/// the index instead of seeking it: ~20s per tick holding the write
/// lock, starving chat persistence past `busy_timeout`. With fresh
/// stats the same pass takes ~0.1s. `PRAGMA optimize` doesn't catch
/// this on SQLite 3.45 (verified), hence a plain `ANALYZE`. It costs
/// well under a second at current scale, so run it on full-scan ticks.
pub fn refresh_planner_stats(conn: &mut SqliteConnection) {
let started = std::time::Instant::now();
match sql_query("ANALYZE").execute(conn) {
Ok(_) => debug!("ANALYZE completed in {:?}", started.elapsed()),
Err(e) => warn!("ANALYZE failed: {:?}", e),
}
}
/// Refresh the `(library_id, rel_path)` back-refs on hash-keyed /// Refresh the `(library_id, rel_path)` back-refs on hash-keyed
/// tables. A back-ref is stale when: /// tables. A back-ref is stale when:
/// - its `content_hash` is non-null, /// - its `content_hash` is non-null,
+2 -2
View File
@@ -27,7 +27,7 @@ where
Err(e) => e, Err(e) => e,
}; };
for attempt in 1..=max_retries { for attempt in 1..=max_retries {
let base = Duration::from_millis(100).saturating_mul(1_u32.pow(attempt - 1)); let base = Duration::from_millis(100).saturating_mul(2_u32.pow(attempt - 1));
let jitter_range = base.as_millis() as f64 * 0.25; let jitter_range = base.as_millis() as f64 * 0.25;
let jitter = rand::thread_rng().gen_range(-jitter_range..=jitter_range) as u128; let jitter = rand::thread_rng().gen_range(-jitter_range..=jitter_range) as u128;
let delay = base.as_millis() as i128 + jitter as i128; let delay = base.as_millis() as i128 + jitter as i128;
@@ -92,7 +92,7 @@ where
); );
break; break;
} }
let base = Duration::from_millis(100).saturating_mul(1_u32.pow(attempt - 1)); let base = Duration::from_millis(100).saturating_mul(2_u32.pow(attempt - 1));
let jitter_range = base.as_millis() as f64 * 0.25; let jitter_range = base.as_millis() as f64 * 0.25;
let jitter = rand::thread_rng().gen_range(-jitter_range..=jitter_range) as u128; let jitter = rand::thread_rng().gen_range(-jitter_range..=jitter_range) as u128;
let delay = base.as_millis() as i128 + jitter as i128; let delay = base.as_millis() as i128 + jitter as i128;
+3
View File
@@ -577,6 +577,9 @@ pub fn watch_files(
// model" for the rules. // model" for the rules.
{ {
let mut conn = image_api::database::connect(); let mut conn = image_api::database::connect();
if is_full_scan {
library_maintenance::refresh_planner_stats(&mut conn);
}
let _ = image_api::database::reconcile::run(&mut conn); let _ = image_api::database::reconcile::run(&mut conn);
// Back-ref refresh: hash-keyed rows whose // Back-ref refresh: hash-keyed rows whose