Skip to content

Commit bb48bbc

Browse files
authored
Merge pull request #3799 from tnull/2025-05-inconsistent-list
Skip any erroring entry in `FilesystemStore::list`
2 parents 66be919 + 6681a69 commit bb48bbc

File tree

1 file changed

+54
-31
lines changed

1 file changed

+54
-31
lines changed

lightning-persister/src/fs_store.rs

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ fn path_to_windows_str<T: AsRef<OsStr>>(path: &T) -> Vec<u16> {
3333
// The number of read/write/remove/list operations after which we clean up our `locks` HashMap.
3434
const GC_LOCK_INTERVAL: usize = 25;
3535

36+
// The number of times we retry listing keys in `FilesystemStore::list` before we give up reaching
37+
// a consistent view and error out.
38+
const LIST_DIR_CONSISTENCY_RETRIES: usize = 10;
39+
3640
/// A [`KVStoreSync`] implementation that writes to and reads from the file system.
3741
pub struct FilesystemStore {
3842
data_dir: PathBuf,
@@ -306,23 +310,45 @@ impl KVStoreSync for FilesystemStore {
306310
check_namespace_key_validity(primary_namespace, secondary_namespace, None, "list")?;
307311

308312
let prefixed_dest = self.get_dest_dir_path(primary_namespace, secondary_namespace)?;
309-
let mut keys = Vec::new();
310313

311314
if !Path::new(&prefixed_dest).exists() {
312315
return Ok(Vec::new());
313316
}
314317

315-
for entry in fs::read_dir(&prefixed_dest)? {
316-
let entry = entry?;
317-
let p = entry.path();
318-
319-
if !dir_entry_is_key(&p)? {
320-
continue;
321-
}
318+
let mut keys;
319+
let mut retries = LIST_DIR_CONSISTENCY_RETRIES;
322320

323-
let key = get_key_from_dir_entry(&p, &prefixed_dest)?;
321+
'retry_list: loop {
322+
keys = Vec::new();
323+
'skip_entry: for entry in fs::read_dir(&prefixed_dest)? {
324+
let entry = entry?;
325+
let p = entry.path();
324326

325-
keys.push(key);
327+
let res = dir_entry_is_key(&entry);
328+
match res {
329+
Ok(true) => {
330+
let key = get_key_from_dir_entry_path(&p, &prefixed_dest)?;
331+
keys.push(key);
332+
},
333+
Ok(false) => {
334+
// We didn't error, but the entry is not a valid key (e.g., a directory,
335+
// or a temp file).
336+
continue 'skip_entry;
337+
},
338+
Err(e) => {
339+
if e.kind() == lightning::io::ErrorKind::NotFound && retries > 0 {
340+
// We had found the entry in `read_dir` above, so some race happend.
341+
// Retry the `read_dir` to get a consistent view.
342+
retries -= 1;
343+
continue 'retry_list;
344+
} else {
345+
// For all errors or if we exhausted retries, bubble up.
346+
return Err(e.into());
347+
}
348+
},
349+
}
350+
}
351+
break 'retry_list;
326352
}
327353

328354
self.garbage_collect_locks();
@@ -331,7 +357,8 @@ impl KVStoreSync for FilesystemStore {
331357
}
332358
}
333359

334-
fn dir_entry_is_key(p: &Path) -> Result<bool, lightning::io::Error> {
360+
fn dir_entry_is_key(dir_entry: &fs::DirEntry) -> Result<bool, lightning::io::Error> {
361+
let p = dir_entry.path();
335362
if let Some(ext) = p.extension() {
336363
#[cfg(target_os = "windows")]
337364
{
@@ -346,14 +373,7 @@ fn dir_entry_is_key(p: &Path) -> Result<bool, lightning::io::Error> {
346373
}
347374
}
348375

349-
let metadata = p.metadata().map_err(|e| {
350-
let msg = format!(
351-
"Failed to list keys at path {}: {}",
352-
PrintableString(p.to_str().unwrap_or_default()),
353-
e
354-
);
355-
lightning::io::Error::new(lightning::io::ErrorKind::Other, msg)
356-
})?;
376+
let metadata = dir_entry.metadata()?;
357377

358378
// We allow the presence of directories in the empty primary namespace and just skip them.
359379
if metadata.is_dir() {
@@ -377,7 +397,7 @@ fn dir_entry_is_key(p: &Path) -> Result<bool, lightning::io::Error> {
377397
Ok(true)
378398
}
379399

380-
fn get_key_from_dir_entry(p: &Path, base_path: &Path) -> Result<String, lightning::io::Error> {
400+
fn get_key_from_dir_entry_path(p: &Path, base_path: &Path) -> Result<String, lightning::io::Error> {
381401
match p.strip_prefix(&base_path) {
382402
Ok(stripped_path) => {
383403
if let Some(relative_path) = stripped_path.to_str() {
@@ -435,24 +455,27 @@ impl MigratableKVStore for FilesystemStore {
435455
let mut keys = Vec::new();
436456

437457
'primary_loop: for primary_entry in fs::read_dir(prefixed_dest)? {
438-
let primary_path = primary_entry?.path();
458+
let primary_entry = primary_entry?;
459+
let primary_path = primary_entry.path();
439460

440-
if dir_entry_is_key(&primary_path)? {
461+
if dir_entry_is_key(&primary_entry)? {
441462
let primary_namespace = String::new();
442463
let secondary_namespace = String::new();
443-
let key = get_key_from_dir_entry(&primary_path, prefixed_dest)?;
464+
let key = get_key_from_dir_entry_path(&primary_path, prefixed_dest)?;
444465
keys.push((primary_namespace, secondary_namespace, key));
445466
continue 'primary_loop;
446467
}
447468

448469
// The primary_entry is actually also a directory.
449470
'secondary_loop: for secondary_entry in fs::read_dir(&primary_path)? {
450-
let secondary_path = secondary_entry?.path();
471+
let secondary_entry = secondary_entry?;
472+
let secondary_path = secondary_entry.path();
451473

452-
if dir_entry_is_key(&secondary_path)? {
453-
let primary_namespace = get_key_from_dir_entry(&primary_path, prefixed_dest)?;
474+
if dir_entry_is_key(&secondary_entry)? {
475+
let primary_namespace =
476+
get_key_from_dir_entry_path(&primary_path, prefixed_dest)?;
454477
let secondary_namespace = String::new();
455-
let key = get_key_from_dir_entry(&secondary_path, &primary_path)?;
478+
let key = get_key_from_dir_entry_path(&secondary_path, &primary_path)?;
456479
keys.push((primary_namespace, secondary_namespace, key));
457480
continue 'secondary_loop;
458481
}
@@ -462,12 +485,12 @@ impl MigratableKVStore for FilesystemStore {
462485
let tertiary_entry = tertiary_entry?;
463486
let tertiary_path = tertiary_entry.path();
464487

465-
if dir_entry_is_key(&tertiary_path)? {
488+
if dir_entry_is_key(&tertiary_entry)? {
466489
let primary_namespace =
467-
get_key_from_dir_entry(&primary_path, prefixed_dest)?;
490+
get_key_from_dir_entry_path(&primary_path, prefixed_dest)?;
468491
let secondary_namespace =
469-
get_key_from_dir_entry(&secondary_path, &primary_path)?;
470-
let key = get_key_from_dir_entry(&tertiary_path, &secondary_path)?;
492+
get_key_from_dir_entry_path(&secondary_path, &primary_path)?;
493+
let key = get_key_from_dir_entry_path(&tertiary_path, &secondary_path)?;
471494
keys.push((primary_namespace, secondary_namespace, key));
472495
} else {
473496
debug_assert!(

0 commit comments

Comments
 (0)