@@ -33,6 +33,10 @@ fn path_to_windows_str<T: AsRef<OsStr>>(path: &T) -> Vec<u16> {
33
33
// The number of read/write/remove/list operations after which we clean up our `locks` HashMap.
34
34
const GC_LOCK_INTERVAL : usize = 25 ;
35
35
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
+
36
40
/// A [`KVStoreSync`] implementation that writes to and reads from the file system.
37
41
pub struct FilesystemStore {
38
42
data_dir : PathBuf ,
@@ -306,23 +310,45 @@ impl KVStoreSync for FilesystemStore {
306
310
check_namespace_key_validity ( primary_namespace, secondary_namespace, None , "list" ) ?;
307
311
308
312
let prefixed_dest = self . get_dest_dir_path ( primary_namespace, secondary_namespace) ?;
309
- let mut keys = Vec :: new ( ) ;
310
313
311
314
if !Path :: new ( & prefixed_dest) . exists ( ) {
312
315
return Ok ( Vec :: new ( ) ) ;
313
316
}
314
317
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 ;
322
320
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 ( ) ;
324
326
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;
326
352
}
327
353
328
354
self . garbage_collect_locks ( ) ;
@@ -331,7 +357,8 @@ impl KVStoreSync for FilesystemStore {
331
357
}
332
358
}
333
359
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 ( ) ;
335
362
if let Some ( ext) = p. extension ( ) {
336
363
#[ cfg( target_os = "windows" ) ]
337
364
{
@@ -346,14 +373,7 @@ fn dir_entry_is_key(p: &Path) -> Result<bool, lightning::io::Error> {
346
373
}
347
374
}
348
375
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 ( ) ?;
357
377
358
378
// We allow the presence of directories in the empty primary namespace and just skip them.
359
379
if metadata. is_dir ( ) {
@@ -377,7 +397,7 @@ fn dir_entry_is_key(p: &Path) -> Result<bool, lightning::io::Error> {
377
397
Ok ( true )
378
398
}
379
399
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 > {
381
401
match p. strip_prefix ( & base_path) {
382
402
Ok ( stripped_path) => {
383
403
if let Some ( relative_path) = stripped_path. to_str ( ) {
@@ -435,24 +455,27 @@ impl MigratableKVStore for FilesystemStore {
435
455
let mut keys = Vec :: new ( ) ;
436
456
437
457
' 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 ( ) ;
439
460
440
- if dir_entry_is_key ( & primary_path ) ? {
461
+ if dir_entry_is_key ( & primary_entry ) ? {
441
462
let primary_namespace = String :: new ( ) ;
442
463
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) ?;
444
465
keys. push ( ( primary_namespace, secondary_namespace, key) ) ;
445
466
continue ' primary_loop;
446
467
}
447
468
448
469
// The primary_entry is actually also a directory.
449
470
' 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 ( ) ;
451
473
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) ?;
454
477
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) ?;
456
479
keys. push ( ( primary_namespace, secondary_namespace, key) ) ;
457
480
continue ' secondary_loop;
458
481
}
@@ -462,12 +485,12 @@ impl MigratableKVStore for FilesystemStore {
462
485
let tertiary_entry = tertiary_entry?;
463
486
let tertiary_path = tertiary_entry. path ( ) ;
464
487
465
- if dir_entry_is_key ( & tertiary_path ) ? {
488
+ if dir_entry_is_key ( & tertiary_entry ) ? {
466
489
let primary_namespace =
467
- get_key_from_dir_entry ( & primary_path, prefixed_dest) ?;
490
+ get_key_from_dir_entry_path ( & primary_path, prefixed_dest) ?;
468
491
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) ?;
471
494
keys. push ( ( primary_namespace, secondary_namespace, key) ) ;
472
495
} else {
473
496
debug_assert ! (
0 commit comments