Skip to content

Commit 5d55c80

Browse files
committed
chore: Update docs for - fix: Segfault on dropping picker mid-rescan
1 parent abd0822 commit 5d55c80

6 files changed

Lines changed: 47 additions & 25 deletions

File tree

crates/fff-core/src/file_picker.rs

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ pub(crate) struct FileSync {
137137
files: StableVec<FileItem>,
138138
indexable_count: usize,
139139
base_count: usize,
140+
/// Number of active present files that exists in the file sytsem
141+
live_count: usize,
140142
/// Sorted directory table. `StableVec` so post-scan snapshots can keep
141143
/// the allocation alive across a picker drop without copying, and so
142144
/// concurrent readers observe a consistent view via the same shared
@@ -159,6 +161,7 @@ impl FileSync {
159161
files: StableVec::from_vec_with_reserve(Vec::new(), MAX_OVERFLOW_FILES),
160162
indexable_count: 0,
161163
base_count: 0,
164+
live_count: 0,
162165
dirs: StableVec::from_vec_with_reserve(Vec::new(), 0),
163166
overflow_builder: None,
164167
git_workdir: None,
@@ -321,10 +324,11 @@ impl FileSync {
321324
overflow_arena
322325
};
323326
if predicate(file, arena) {
324-
file.delete();
327+
file.set_deleted(true);
325328
tombstoned += 1;
326329
}
327330
}
331+
self.live_count -= tombstoned;
328332
tombstoned
329333
}
330334
}
@@ -566,6 +570,12 @@ impl FilePicker {
566570
self.sync_data.files()
567571
}
568572

573+
/// Count of live (non-tombstoned) files. O(1).
574+
#[inline]
575+
pub fn live_file_count(&self) -> usize {
576+
self.sync_data.live_count
577+
}
578+
569579
pub fn get_overflow_files(&self) -> &[FileItem] {
570580
self.sync_data.overflow_files()
571581
}
@@ -871,7 +881,7 @@ impl FilePicker {
871881
"Fuzzy search",
872882
);
873883

874-
let total_files = files.len();
884+
let total_files = self.live_file_count();
875885
let location = query.location;
876886

877887
// Get effective query for max_typos calculation (without location suffix)
@@ -1060,7 +1070,7 @@ impl FilePicker {
10601070
items: vec![],
10611071
scores: vec![],
10621072
total_matched,
1063-
total_files: self.sync_data.files().len(),
1073+
total_files: self.live_file_count(),
10641074
total_dirs,
10651075
location,
10661076
};
@@ -1074,7 +1084,7 @@ impl FilePicker {
10741084
items,
10751085
scores,
10761086
total_matched,
1077-
total_files: self.sync_data.files().len(),
1087+
total_files: self.live_file_count(),
10781088
total_dirs,
10791089
location,
10801090
};
@@ -1422,7 +1432,7 @@ impl FilePicker {
14221432
"File market for modification doesn't exists or not accessible"
14231433
)
14241434
})
1425-
.ok()?; // if we can't read metadata this file either doesn't exists or not accessible
1435+
.ok()?;
14261436

14271437
let size = metadata.len();
14281438
let modified_time = metadata
@@ -1431,7 +1441,8 @@ impl FilePicker {
14311441
.and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
14321442
.map(|d| d.as_secs());
14331443

1434-
if file.is_deleted() {
1444+
let was_deleted = file.is_deleted();
1445+
if was_deleted {
14351446
file.set_deleted(false);
14361447
}
14371448

@@ -1451,7 +1462,13 @@ impl FilePicker {
14511462
}
14521463
}
14531464

1454-
Some(&*self.sync_data.get_file_mut(pos)?)
1465+
// Increment after dropping the mutable file reference so the
1466+
// reborrow for the return doesn't conflict.
1467+
if was_deleted {
1468+
self.sync_data.live_count += 1;
1469+
}
1470+
1471+
self.sync_data.files().get(pos)
14551472
}
14561473

14571474
/// Adds a new file to picker, if the file can not be added returns `None`
@@ -1495,6 +1512,7 @@ impl FilePicker {
14951512
return None;
14961513
}
14971514

1515+
self.sync_data.live_count += 1;
14981516
self.sync_data.files.last()
14991517
}
15001518

@@ -1505,19 +1523,12 @@ impl FilePicker {
15051523
Ok(index) => {
15061524
let file = &mut self.sync_data.files[index];
15071525
file.set_deleted(true);
1508-
// Clear any cached git status — the tombstone no longer
1509-
// corresponds to a real worktree file, so any previously
1510-
// cached status (e.g. `WT_MODIFIED` from before the delete)
1511-
// is actively misleading. All user-facing search
1512-
// paths filter `is_deleted()` so this is invisible today,
1513-
// but keeping the invariant "tombstone ⇒ git_status=None"
1514-
// means a new reader that forgets the filter can't leak
1515-
// stale data.
15161526
file.git_status = None;
15171527
file.invalidate_mmap(&self.cache_budget);
15181528
if let Some(ref overlay) = self.sync_data.bigram_overlay {
15191529
overlay.write().delete_file(index);
15201530
}
1531+
self.sync_data.live_count -= 1;
15211532
true
15221533
}
15231534
Err(_) => {
@@ -1528,6 +1539,7 @@ impl FilePicker {
15281539
file.set_deleted(true);
15291540
file.git_status = None;
15301541
file.invalidate_mmap(&self.cache_budget);
1542+
self.sync_data.live_count -= 1;
15311543
true
15321544
} else {
15331545
false
@@ -1979,6 +1991,7 @@ impl FileSync {
19791991
files: StableVec::from_vec_with_reserve(files, MAX_OVERFLOW_FILES),
19801992
indexable_count,
19811993
base_count,
1994+
live_count: base_count,
19821995
dirs: StableVec::from_vec_with_reserve(dirs, 0),
19831996
overflow_builder: None,
19841997
git_workdir,

crates/fff-core/src/grep.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{
1111
constraints::apply_constraints,
1212
extract_bigrams,
1313
sort_buffer::sort_with_buffer,
14-
types::{ContentCacheBudget, FileItem},
14+
types::{ContentCacheBudget, FileItem, FileSliceExt},
1515
};
1616
use aho_corasick::AhoCorasick;
1717
pub use fff_grep::{
@@ -960,7 +960,7 @@ pub(crate) fn multi_grep_search<'a>(
960960
arena: crate::simd_path::ArenaPtr,
961961
overflow_arena: crate::simd_path::ArenaPtr,
962962
) -> GrepResult<'a> {
963-
let total_files = files.len();
963+
let total_files = files.live_count();
964964

965965
if patterns.is_empty() || patterns.iter().all(|p| p.is_empty()) {
966966
return GrepResult {
@@ -1859,7 +1859,7 @@ pub(crate) fn grep_search<'a>(
18591859
arena: crate::simd_path::ArenaPtr,
18601860
overflow_arena: crate::simd_path::ArenaPtr,
18611861
) -> GrepResult<'a> {
1862-
let total_files = files.len();
1862+
let total_files = files.live_count();
18631863

18641864
// Extract the grep text and file constraints from the parsed query.
18651865
// For grep, the search pattern is the original query with constraint tokens

crates/fff-core/src/stable_vec.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,10 @@ impl<T> StableVec<T> {
9999
true
100100
}
101101

102+
// this method is specifically private becuase you probably need to use
103+
// live_count if you are trying to access this method
102104
#[inline]
103-
pub fn len(&self) -> usize {
105+
fn len(&self) -> usize {
104106
self.inner.len.load(Ordering::Acquire)
105107
}
106108

crates/fff-core/src/types.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ impl FFFStringStorage for ArenaPtr {
3838
}
3939
}
4040

41+
pub trait FileSliceExt {
42+
fn live_count(&self) -> usize;
43+
}
44+
45+
impl FileSliceExt for [FileItem] {
46+
#[inline]
47+
fn live_count(&self) -> usize {
48+
self.iter().filter(|f| !f.is_deleted()).count()
49+
}
50+
}
51+
4152
/// Cached file contents — mmap on Unix, heap buffer on Windows.
4253
///
4354
/// On Windows, memory-mapped files hold the file handle open and prevent

crates/fff-core/tests/drop_during_post_scan.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,7 @@ fn drop_picker_concurrent_with_search_no_segfault() {
122122
if let Ok(guard) = sp_clone.read() {
123123
if let Some(picker) = guard.as_ref() {
124124
let query = parser.parse("func");
125-
let _ = picker.fuzzy_search(
126-
&query,
127-
None,
128-
FuzzySearchOptions::default(),
129-
);
125+
let _ = picker.fuzzy_search(&query, None, FuzzySearchOptions::default());
130126
}
131127
}
132128
std::thread::sleep(Duration::from_millis(1));

doc/fff.nvim.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
*fff.nvim.txt*
2-
For Neovim >= 0.10.0 Last change: 2026 May 08
2+
For Neovim >= 0.10.0 Last change: 2026 May 11
33

44
==============================================================================
55
Table of Contents *fff.nvim-table-of-contents*

0 commit comments

Comments
 (0)