Skip to content

Commit 004f302

Browse files
committed
Drive indexing: fix bg scan_subtree failures
Two bugs in `run_background_verification` caused `scan_subtree` to fail for every newly discovered directory: 1. `verify_affected_dirs` sends `UpsertEntryV2` for new dirs, but thosewrites were still queued when `scan_subtree` tried to resolve the path. Fix: flush the writer before starting subtree scans. 2. `ScanContext::new` called `ensure_root_sentinel` (a write) on a connection that contended with the writer thread's write lock, causing "database is locked" after a 5s timeout. Fix: skip the sentinel for subtree scans (it already exists) and use a read-only connection. Also adds `IndexStore::open_read_connection` for WAL-mode read-only access.
1 parent ef0f049 commit 004f302

3 files changed

Lines changed: 31 additions & 5 deletions

File tree

apps/desktop/src-tauri/src/indexing/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,13 @@ async fn run_background_verification(
12101210
// Scan newly discovered directories (inserts children + computes subtree aggregates).
12111211
// Skip excluded paths (system dirs like /System, /dev) that aren't in the index.
12121212
if !verify_result.new_dir_paths.is_empty() {
1213+
// Flush first: verify_affected_dirs sent UpsertEntryV2 for each new dir, but those
1214+
// writes are still queued. scan_subtree opens a read connection to resolve the dir's
1215+
// path → entry_id, which fails if the entry isn't committed yet.
1216+
if let Err(e) = writer.flush().await {
1217+
log::warn!("Background verification pre-scan flush failed: {e}");
1218+
}
1219+
12131220
let cancelled = AtomicBool::new(false);
12141221
for dir_path in &verify_result.new_dir_paths {
12151222
if scanner::should_exclude(dir_path) {

apps/desktop/src-tauri/src/indexing/scanner.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,11 +259,16 @@ fn run_scan(
259259
let mut total_dirs: u64 = 0;
260260

261261
// Initialize the scan context: seed root mapping and get next_id from DB.
262-
// We need a temporary read connection to fetch next_id. The writer thread
263-
// owns the write connection, but we only need a read here.
262+
// Volume-root scans need a write connection (to create the root sentinel).
263+
// Subtree scans only read (next_id + resolve_path), so use a read connection
264+
// to avoid contending with the writer thread's write lock.
264265
let mut scan_ctx = {
265266
let db_path = writer.db_path();
266-
let conn = IndexStore::open_write_connection(&db_path).map_err(|e| ScanError::WriterSend(e.to_string()))?;
267+
let conn = if is_volume_root {
268+
IndexStore::open_write_connection(&db_path).map_err(|e| ScanError::WriterSend(e.to_string()))?
269+
} else {
270+
IndexStore::open_read_connection(&db_path).map_err(|e| ScanError::WriterSend(e.to_string()))?
271+
};
267272
conn.busy_timeout(std::time::Duration::from_secs(5))
268273
.map_err(|e| ScanError::WriterSend(e.to_string()))?;
269274
ScanContext::new(&conn, root, is_volume_root).map_err(|e| ScanError::WriterSend(e.to_string()))?

apps/desktop/src-tauri/src/indexing/store.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,12 @@ impl ScanContext {
8686
/// Returns an error if the root isn't indexed yet (for example, a micro-scan
8787
/// racing with an ongoing full scan — the full scan will cover it).
8888
pub fn new(conn: &Connection, root: &Path, is_volume_root: bool) -> Result<Self, IndexStoreError> {
89-
// Ensure the root sentinel exists
90-
ensure_root_sentinel(conn)?;
89+
// Only volume-root scans need to create the sentinel — subtree scans
90+
// run after the full scan has already inserted it, and their connection
91+
// may be read-only or contending with the writer thread's write lock.
92+
if is_volume_root {
93+
ensure_root_sentinel(conn)?;
94+
}
9195

9296
let next_id = IndexStore::get_next_id(conn)?;
9397

@@ -436,6 +440,16 @@ impl IndexStore {
436440
Ok(conn)
437441
}
438442

443+
/// Open a read-only connection with WAL pragmas and `platform_case` collation.
444+
///
445+
/// Never contends with the writer thread's write lock.
446+
pub fn open_read_connection(db_path: &Path) -> Result<Connection, IndexStoreError> {
447+
let conn = Connection::open_with_flags(db_path, rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY)?;
448+
register_platform_case_collation(&conn)?;
449+
apply_pragmas(&conn)?;
450+
Ok(conn)
451+
}
452+
439453
/// Read all meta keys and return the index status.
440454
pub fn get_index_status(&self) -> Result<IndexStatus, IndexStoreError> {
441455
Ok(IndexStatus {

0 commit comments

Comments
 (0)