@@ -29,14 +29,20 @@ where
29
29
/// unspecified if this helper is called concurrently.
30
30
/// - This helper is not robust against TOCTOU problems.
31
31
///
32
- /// FIXME: this implementation is insufficiently robust to replace bootstrap's clean `rm_rf`
33
- /// implementation:
34
- ///
35
- /// - This implementation currently does not perform retries.
32
+ /// FIXME: Audit whether this implementation is robust enough to replace bootstrap's clean `rm_rf`.
36
33
#[ track_caller]
37
34
pub fn recursive_remove < P : AsRef < Path > > ( path : P ) -> io:: Result < ( ) > {
38
35
let path = path. as_ref ( ) ;
39
- let metadata = fs:: symlink_metadata ( path) ?;
36
+
37
+ // If the path doesn't exist, we treat it as a successful no-op.
38
+ // From the caller's perspective, the goal is simply "ensure this file/dir is gone" —
39
+ // if it's already not there, that's a success, not an error.
40
+ let metadata = match fs:: symlink_metadata ( path) {
41
+ Ok ( m) => m,
42
+ Err ( e) if e. kind ( ) == io:: ErrorKind :: NotFound => return Ok ( ( ) ) ,
43
+ Err ( e) => return Err ( e) ,
44
+ } ;
45
+
40
46
#[ cfg( windows) ]
41
47
let is_dir_like = |meta : & fs:: Metadata | {
42
48
use std:: os:: windows:: fs:: FileTypeExt ;
@@ -45,11 +51,34 @@ pub fn recursive_remove<P: AsRef<Path>>(path: P) -> io::Result<()> {
45
51
#[ cfg( not( windows) ) ]
46
52
let is_dir_like = fs:: Metadata :: is_dir;
47
53
48
- if is_dir_like ( & metadata) {
49
- fs:: remove_dir_all ( path)
50
- } else {
51
- try_remove_op_set_perms ( fs:: remove_file, path, metadata)
54
+ const MAX_RETRIES : usize = 5 ;
55
+ const RETRY_DELAY_MS : u64 = 10 ;
56
+
57
+ let try_remove = || {
58
+ if is_dir_like ( & metadata) {
59
+ fs:: remove_dir_all ( path)
60
+ } else {
61
+ try_remove_op_set_perms ( fs:: remove_file, path, metadata. clone ( ) )
62
+ }
63
+ } ;
64
+
65
+ // Retry deletion a few times to handle transient filesystem errors.
66
+ // This is unusual for local file operations, but it's a safeguard against
67
+ // edge cases like temporary locks (e.g., on Windows), slow antivirus scans,
68
+ // or race conditions where another process/thread is interacting with the file.
69
+ for attempt in 0 ..MAX_RETRIES {
70
+ match try_remove ( ) {
71
+ Ok ( ( ) ) => return Ok ( ( ) ) ,
72
+ Err ( e) if e. kind ( ) == io:: ErrorKind :: NotFound => return Ok ( ( ) ) ,
73
+ Err ( _) if attempt < MAX_RETRIES - 1 => {
74
+ std:: thread:: sleep ( std:: time:: Duration :: from_millis ( RETRY_DELAY_MS ) ) ;
75
+ continue ;
76
+ }
77
+ Err ( e) => return Err ( e) ,
78
+ }
52
79
}
80
+
81
+ Ok ( ( ) )
53
82
}
54
83
55
84
fn try_remove_op_set_perms < ' p , Op > ( mut op : Op , path : & ' p Path , metadata : Metadata ) -> io:: Result < ( ) >
0 commit comments