@@ -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.
3434const 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.
3741pub 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