File tree Expand file tree Collapse file tree
apps/desktop/src-tauri/src/indexing Expand file tree Collapse file tree Original file line number Diff line number Diff 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) {
Original file line number Diff line number Diff 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 ( ) ) ) ?
Original file line number Diff line number Diff 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 {
You can’t perform that action at this time.
0 commit comments