Skip to content

Commit 1039663

Browse files
committed
Implement loop retrying list to achive a consisten view
Previously, we might hit a certain race condition between `fs::read_dir` and actually accessing the directory entry in `FilesystemStore::list`. Here, we introduce a `loop` retrying the listing (by default 3 times) when we suddenly hit a `NotFound` error, to achive a consistent view.
1 parent 2b8d90a commit 1039663

File tree

1 file changed

+36
-10
lines changed

1 file changed

+36
-10
lines changed

lightning-persister/src/fs_store.rs

Lines changed: 36 additions & 10 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 = 3;
39+
3640
/// A [`KVStore`] implementation that writes to and reads from the file system.
3741
pub struct FilesystemStore {
3842
data_dir: PathBuf,
@@ -306,23 +310,45 @@ impl KVStore 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+
let mut keys;
319+
let mut retries = LIST_DIR_CONSISTENCY_RETRIES;
318320

319-
if !dir_entry_is_key(&entry)? {
320-
continue;
321-
}
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();
322326

323-
let key = git_key_from_dir_entry_path(&p, &prefixed_dest)?;
324-
325-
keys.push(key);
327+
let res = dir_entry_is_key(&entry);
328+
match res {
329+
Ok(true) => {
330+
let key = git_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, or
335+
// 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();

0 commit comments

Comments
 (0)