@@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
22use std:: sync:: { Arc , RwLock , RwLockReadGuard , RwLockWriteGuard , Weak } ;
33use std:: time:: { Duration , Instant } ;
44
5- use crate :: dbs:: lmdb:: spawn_lmdb_gc;
5+ use crate :: dbs:: lmdb:: { LmdbStore , spawn_lmdb_gc} ;
66use crate :: error:: Error ;
77use crate :: file_picker:: FilePicker ;
88use crate :: frecency:: FrecencyTracker ;
@@ -38,6 +38,19 @@ fn wait_for_git_index_lock_release(git_root: &Path) {
3838 }
3939}
4040
41+ /// Poll `done` every 10ms until it returns `true`, or until `timeout` elapses.
42+ /// Returns `true` if the condition was met, `false` on timeout.
43+ fn poll_until ( timeout : Duration , mut done : impl FnMut ( ) -> bool ) -> bool {
44+ let start = Instant :: now ( ) ;
45+ while !done ( ) {
46+ if start. elapsed ( ) >= timeout {
47+ return false ;
48+ }
49+ std:: thread:: sleep ( Duration :: from_millis ( 10 ) ) ;
50+ }
51+ true
52+ }
53+
4154/// Thread-safe shared handle to the [`FilePicker`] instance.
4255/// This accumulates only asynchronous non-blocking operations against the
4356/// file picker: creating, triggering various rescans and so on.
@@ -125,14 +138,9 @@ impl SharedFilePicker {
125138 }
126139 } ;
127140
128- let start = std:: time:: Instant :: now ( ) ;
129- while signal. load ( std:: sync:: atomic:: Ordering :: Acquire ) {
130- if start. elapsed ( ) >= timeout {
131- return false ;
132- }
133- std:: thread:: sleep ( Duration :: from_millis ( 10 ) ) ;
134- }
135- true
141+ poll_until ( timeout, || {
142+ !signal. load ( std:: sync:: atomic:: Ordering :: Acquire )
143+ } )
136144 }
137145
138146 /// Block until the background file watcher is ready.
@@ -146,14 +154,9 @@ impl SharedFilePicker {
146154 }
147155 } ;
148156
149- let start = std:: time:: Instant :: now ( ) ;
150- while !watch_ready_signal. load ( std:: sync:: atomic:: Ordering :: Acquire ) {
151- if start. elapsed ( ) >= timeout {
152- return false ;
153- }
154- std:: thread:: sleep ( Duration :: from_millis ( 10 ) ) ;
155- }
156- true
157+ poll_until ( timeout, || {
158+ watch_ready_signal. load ( std:: sync:: atomic:: Ordering :: Acquire )
159+ } )
157160 }
158161
159162 /// Blocks until both the filesystem walk and post-scan indexing are done.
@@ -170,18 +173,10 @@ impl SharedFilePicker {
170173 }
171174 } ;
172175
173- let start = std:: time:: Instant :: now ( ) ;
174- loop {
175- if start. elapsed ( ) >= timeout {
176- return false ;
177- }
178- let s = scanning. load ( std:: sync:: atomic:: Ordering :: Acquire ) ;
179- let p = post_scan_active. load ( std:: sync:: atomic:: Ordering :: Acquire ) ;
180- if !s && !p {
181- return true ;
182- }
183- std:: thread:: sleep ( Duration :: from_millis ( 10 ) ) ;
184- }
176+ poll_until ( timeout, || {
177+ !scanning. load ( std:: sync:: atomic:: Ordering :: Acquire )
178+ && !post_scan_active. load ( std:: sync:: atomic:: Ordering :: Acquire )
179+ } )
185180 }
186181
187182 /// Trigger a full filesystem rescan without blocking the caller.
@@ -252,14 +247,29 @@ impl SharedFilePicker {
252247 }
253248}
254249
255- /// Thread-safe shared handle to the [`FrecencyTracker`] instance.
256- #[ derive( Clone ) ]
257- pub struct SharedFrecency {
258- inner : Arc < RwLock < Option < FrecencyTracker > > > ,
250+ /// Thread-safe shared handle to an LMDB-backed store. A disabled (`noop`)
251+ /// instance silently ignores writes. See the [`SharedFrecency`] and
252+ /// [`SharedQueryTracker`] aliases.
253+ ///
254+ /// `LmdbStore` is intentionally crate-private, so the store type is sealed:
255+ /// only `FrecencyTracker` / `QueryTracker` can ever instantiate this.
256+ #[ allow( private_bounds) ]
257+ pub struct SharedDb < T : LmdbStore > {
258+ inner : Arc < RwLock < Option < T > > > ,
259259 enabled : bool ,
260260}
261261
262- impl Default for SharedFrecency {
262+ // Hand-written to avoid a spurious `T: Clone` bound — `Arc` is always `Clone`.
263+ impl < T : LmdbStore > Clone for SharedDb < T > {
264+ fn clone ( & self ) -> Self {
265+ Self {
266+ inner : self . inner . clone ( ) ,
267+ enabled : self . enabled ,
268+ }
269+ }
270+ }
271+
272+ impl < T : LmdbStore > Default for SharedDb < T > {
263273 fn default ( ) -> Self {
264274 Self {
265275 inner : Arc :: new ( RwLock :: new ( None ) ) ,
@@ -268,13 +278,14 @@ impl Default for SharedFrecency {
268278 }
269279}
270280
271- impl std:: fmt:: Debug for SharedFrecency {
281+ impl < T : LmdbStore > std:: fmt:: Debug for SharedDb < T > {
272282 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
273- f. debug_tuple ( "SharedFrecency " ) . field ( & ".." ) . finish ( )
283+ f. debug_tuple ( "SharedDb " ) . field ( & T :: LABEL ) . finish ( )
274284 }
275285}
276286
277- impl SharedFrecency {
287+ #[ allow( private_bounds) ]
288+ impl < T : LmdbStore > SharedDb < T > {
278289 /// Creates a disabled instance that silently ignores all writes.
279290 pub fn noop ( ) -> Self {
280291 Self {
@@ -283,15 +294,16 @@ impl SharedFrecency {
283294 }
284295 }
285296
286- pub fn read ( & self ) -> Result < RwLockReadGuard < ' _ , Option < FrecencyTracker > > , Error > {
297+ pub fn read ( & self ) -> Result < RwLockReadGuard < ' _ , Option < T > > , Error > {
287298 self . inner . read ( ) . map_err ( |_| Error :: AcquireFrecencyLock )
288299 }
289300
290- pub fn write ( & self ) -> Result < RwLockWriteGuard < ' _ , Option < FrecencyTracker > > , Error > {
301+ pub fn write ( & self ) -> Result < RwLockWriteGuard < ' _ , Option < T > > , Error > {
291302 self . inner . write ( ) . map_err ( |_| Error :: AcquireFrecencyLock )
292303 }
293304
294- pub fn init ( & self , tracker : FrecencyTracker ) -> Result < ( ) , Error > {
305+ /// Initialize the store + spawn GC in the background. No-op when disabled.
306+ pub fn init ( & self , tracker : T ) -> Result < ( ) , Error > {
295307 if !self . enabled {
296308 return Ok ( ( ) ) ;
297309 }
@@ -320,7 +332,7 @@ impl SharedFrecency {
320332 let Some ( tracker) = guard. take ( ) else {
321333 return Ok ( None ) ;
322334 } ;
323- let db_path = tracker. db_path ( ) . to_path_buf ( ) ;
335+ let db_path = tracker. env ( ) . path ( ) . to_path_buf ( ) ;
324336 // Drop closes the LMDB env and unmaps the files
325337 drop ( tracker) ;
326338 drop ( guard) ;
@@ -332,80 +344,8 @@ impl SharedFrecency {
332344 }
333345}
334346
335- /// Thread-safe shared handle to the [`QueryTracker`] instance.
336- #[ derive( Clone ) ]
337- pub struct SharedQueryTracker {
338- inner : Arc < RwLock < Option < QueryTracker > > > ,
339- enabled : bool ,
340- }
341-
342- impl Default for SharedQueryTracker {
343- fn default ( ) -> Self {
344- Self {
345- inner : Arc :: new ( RwLock :: new ( None ) ) ,
346- enabled : true ,
347- }
348- }
349- }
350-
351- impl std:: fmt:: Debug for SharedQueryTracker {
352- fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
353- f. debug_tuple ( "SharedQueryTracker" ) . field ( & ".." ) . finish ( )
354- }
355- }
356-
357- impl SharedQueryTracker {
358- /// Creates a disabled instance that silently ignores all writes.
359- pub fn noop ( ) -> Self {
360- Self {
361- inner : Arc :: new ( RwLock :: new ( None ) ) ,
362- enabled : false ,
363- }
364- }
365-
366- pub fn read ( & self ) -> Result < RwLockReadGuard < ' _ , Option < QueryTracker > > , Error > {
367- self . inner . read ( ) . map_err ( |_| Error :: AcquireFrecencyLock )
368- }
369-
370- pub fn write ( & self ) -> Result < RwLockWriteGuard < ' _ , Option < QueryTracker > > , Error > {
371- self . inner . write ( ) . map_err ( |_| Error :: AcquireFrecencyLock )
372- }
373-
374- /// Initialize the query tracker + spawn GC in the background.
375- /// No-op if this is a disabled instance.
376- pub fn init ( & self , tracker : QueryTracker ) -> Result < ( ) , Error > {
377- if !self . enabled {
378- return Ok ( ( ) ) ;
379- }
380- {
381- let mut guard = self . write ( ) ?;
382- * guard = Some ( tracker) ;
383- }
384-
385- spawn_lmdb_gc ( self . inner . clone ( ) ) ;
386- Ok ( ( ) )
387- }
347+ /// Thread-safe shared handle to the [`FrecencyTracker`] instance.
348+ pub type SharedFrecency = SharedDb < FrecencyTracker > ;
388349
389- ///Drop the in-memory tracker and delete the on-disk database directory.
390- ///
391- /// Acquires the write lock, ensuring all readers (including any active mmap
392- /// access) are finished before the LMDB environment is closed and the files
393- /// are removed.
394- ///
395- /// Returns `Ok(Some(path))` with the deleted path, or `Ok(None)` if no
396- /// tracker was initialized.
397- pub fn destroy ( & self ) -> Result < Option < PathBuf > , Error > {
398- let mut guard = self . write ( ) ?;
399- let Some ( tracker) = guard. take ( ) else {
400- return Ok ( None ) ;
401- } ;
402- let db_path = tracker. db_path ( ) . to_path_buf ( ) ;
403- drop ( tracker) ;
404- drop ( guard) ;
405- std:: fs:: remove_dir_all ( & db_path) . map_err ( |source| Error :: RemoveDbDir {
406- path : db_path. clone ( ) ,
407- source,
408- } ) ?;
409- Ok ( Some ( db_path) )
410- }
411- }
350+ /// Thread-safe shared handle to the [`QueryTracker`] instance.
351+ pub type SharedQueryTracker = SharedDb < QueryTracker > ;
0 commit comments