Skip to content

Commit 5a73331

Browse files
committed
refactor(core): generic SharedDb<T> replaces the two LMDB handles
SharedFrecency and SharedQueryTracker are now type aliases over a single SharedDb<T: LmdbStore>, and the three wait_for_* methods share one poll_until helper. Public API unchanged.
1 parent 957f222 commit 5a73331

1 file changed

Lines changed: 57 additions & 117 deletions

File tree

crates/fff-core/src/shared.rs

Lines changed: 57 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
22
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak};
33
use std::time::{Duration, Instant};
44

5-
use crate::dbs::lmdb::spawn_lmdb_gc;
5+
use crate::dbs::lmdb::{LmdbStore, spawn_lmdb_gc};
66
use crate::error::Error;
77
use crate::file_picker::FilePicker;
88
use 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

Comments
 (0)