@@ -2089,6 +2089,10 @@ pub const Dir = struct {
20892089 FileBusy ,
20902090 DeviceBusy ,
20912091
2092+ /// The number of retries to delete thought-to-be-empty directories
2093+ /// exceeded `max_retries`.
2094+ TooManyRetries ,
2095+
20922096 /// One of the path components was not a directory.
20932097 /// This error is unreachable if `sub_path` does not contain a path separator.
20942098 NotDir ,
@@ -2101,17 +2105,33 @@ pub const Dir = struct {
21012105 BadPathName ,
21022106 } || os .UnexpectedError ;
21032107
2108+ pub const DeleteTreeOptions = struct {
2109+ /// After successfully deleting all of a directories children, it is possible for
2110+ /// the directory to not actually be empty (either from a new child being added while
2111+ /// iterating or a child being in a `DELETE_PENDING` state on Windows). In this scenario,
2112+ /// the directory will be iterated again and then will be attempted to be deleted again.
2113+ /// This provides a limit to the total number of such retries.
2114+ /// In `deleteTree`, the retry limit is per-directory.
2115+ /// In `deleteTreeMinStackSize`, the retry limit is function-wide.
2116+ max_retries : usize = 4 ,
2117+
2118+ /// When initially trying to delete `sub_path`, it will first try and delete it as
2119+ /// the provided kind.
2120+ kind_hint : File.Kind = .File ,
2121+ };
2122+
21042123 /// Whether `full_path` describes a symlink, file, or directory, this function
21052124 /// removes it. If it cannot be removed because it is a non-empty directory,
21062125 /// this function recursively removes its entries and then tries again.
21072126 /// This operation is not atomic on most file systems.
2108- pub fn deleteTree (self : Dir , sub_path : []const u8 ) DeleteTreeError ! void {
2109- var initial_iterable_dir = (try self .deleteTreeOpenInitialSubpath (sub_path , .File )) orelse return ;
2127+ pub fn deleteTree (self : Dir , sub_path : []const u8 , options : DeleteTreeOptions ) DeleteTreeError ! void {
2128+ var initial_iterable_dir = (try self .deleteTreeOpenInitialSubpath (sub_path , options . kind_hint )) orelse return ;
21102129
21112130 const StackItem = struct {
21122131 name : []const u8 ,
21132132 parent_dir : Dir ,
21142133 iter : IterableDir.Iterator ,
2134+ retries_remaining : usize ,
21152135 };
21162136
21172137 var stack = std .BoundedArray (StackItem , 16 ){};
@@ -2125,6 +2145,7 @@ pub const Dir = struct {
21252145 .name = sub_path ,
21262146 .parent_dir = self ,
21272147 .iter = initial_iterable_dir .iterateAssumeFirstIteration (),
2148+ .retries_remaining = options .max_retries ,
21282149 });
21292150
21302151 process_stack : while (stack .len != 0 ) {
@@ -2162,10 +2183,14 @@ pub const Dir = struct {
21622183 .name = entry .name ,
21632184 .parent_dir = top .iter .dir ,
21642185 .iter = iterable_dir .iterateAssumeFirstIteration (),
2186+ .retries_remaining = options .max_retries ,
21652187 });
21662188 continue :process_stack ;
21672189 } else | _ | {
2168- try top .iter .dir .deleteTreeMinStackSizeWithKindHint (entry .name , entry .kind );
2190+ try top .iter .dir .deleteTreeMinStackSize (entry .name , .{
2191+ .max_retries = options .max_retries ,
2192+ .kind_hint = entry .kind ,
2193+ });
21692194 break :handle_entry ;
21702195 }
21712196 } else {
@@ -2207,6 +2232,7 @@ pub const Dir = struct {
22072232 // pop the value from the stack.
22082233 const parent_dir = top .parent_dir ;
22092234 const name = top .name ;
2235+ const retries_remaining = top .retries_remaining ;
22102236 _ = stack .pop ();
22112237
22122238 var need_to_retry : bool = false ;
@@ -2217,6 +2243,7 @@ pub const Dir = struct {
22172243 };
22182244
22192245 if (need_to_retry ) {
2246+ if (retries_remaining == 0 ) return error .TooManyRetries ;
22202247 // Since we closed the handle that the previous iterator used, we
22212248 // need to re-open the dir and re-create the iterator.
22222249 var iterable_dir = iterable_dir : {
@@ -2282,6 +2309,7 @@ pub const Dir = struct {
22822309 .name = name ,
22832310 .parent_dir = parent_dir ,
22842311 .iter = iterable_dir .iterateAssumeFirstIteration (),
2312+ .retries_remaining = retries_remaining - 1 ,
22852313 });
22862314 continue :process_stack ;
22872315 }
@@ -2290,13 +2318,10 @@ pub const Dir = struct {
22902318
22912319 /// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
22922320 /// This is slower than `deleteTree` but uses less stack space.
2293- pub fn deleteTreeMinStackSize (self : Dir , sub_path : []const u8 ) DeleteTreeError ! void {
2294- return self .deleteTreeMinStackSizeWithKindHint (sub_path , .File );
2295- }
2296-
2297- fn deleteTreeMinStackSizeWithKindHint (self : Dir , sub_path : []const u8 , kind_hint : File.Kind ) DeleteTreeError ! void {
2321+ pub fn deleteTreeMinStackSize (self : Dir , sub_path : []const u8 , options : DeleteTreeOptions ) DeleteTreeError ! void {
2322+ var retries_remaining = options .max_retries ;
22982323 start_over : while (true ) {
2299- var iterable_dir = (try self .deleteTreeOpenInitialSubpath (sub_path , kind_hint )) orelse return ;
2324+ var iterable_dir = (try self .deleteTreeOpenInitialSubpath (sub_path , options . kind_hint )) orelse return ;
23002325 var cleanup_dir_parent : ? IterableDir = null ;
23012326 defer if (cleanup_dir_parent ) | * d | d .close ();
23022327
@@ -2386,14 +2411,22 @@ pub const Dir = struct {
23862411 if (cleanup_dir_parent ) | d | {
23872412 d .dir .deleteDir (dir_name ) catch | err | switch (err ) {
23882413 // These two things can happen due to file system race conditions.
2389- error .FileNotFound , error .DirNotEmpty = > continue :start_over ,
2414+ error .FileNotFound , error .DirNotEmpty = > {
2415+ if (retries_remaining == 0 ) return error .TooManyRetries ;
2416+ retries_remaining -= 1 ;
2417+ continue :start_over ;
2418+ },
23902419 else = > | e | return e ,
23912420 };
23922421 continue :start_over ;
23932422 } else {
23942423 self .deleteDir (sub_path ) catch | err | switch (err ) {
23952424 error .FileNotFound = > return ,
2396- error .DirNotEmpty = > continue :start_over ,
2425+ error .DirNotEmpty = > {
2426+ if (retries_remaining == 0 ) return error .TooManyRetries ;
2427+ retries_remaining -= 1 ;
2428+ continue :start_over ;
2429+ },
23972430 else = > | e | return e ,
23982431 };
23992432 return ;
@@ -2834,7 +2867,7 @@ pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
28342867 var dir = try cwd ().openDir (dirname , .{});
28352868 defer dir .close ();
28362869
2837- return dir .deleteTree (path .basename (absolute_path ));
2870+ return dir .deleteTree (path .basename (absolute_path ), .{} );
28382871}
28392872
28402873/// Same as `Dir.readLink`, except it asserts the path is absolute.
0 commit comments