From cc3c4d1069c7b60a5588cb348f9fd927772b069b Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Tue, 14 Oct 2025 23:16:26 -0400 Subject: [PATCH] windows: workaround kernel race condition in more places --- test/standalone/windows_spawn/main.zig | 38 ++++++++++++++------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index b51e09451c..8802f6efff 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -71,19 +71,7 @@ pub fn main() anyerror!void { try testExec(allocator, "heLLo", "hello from exe\n"); // now rename the exe to not have an extension - { - var attempt: u5 = 0; - while (true) break tmp.dir.rename("hello.exe", "hello") catch |err| switch (err) { - error.AccessDenied => { - if (attempt == 13) return error.AccessDenied; - // give the kernel a chance to finish closing the executable handle - std.os.windows.kernel32.Sleep(@as(u32, 1) << attempt >> 1); - attempt += 1; - continue; - }, - else => |e| return e, - }; - } + try renameExe(tmp.dir, "hello.exe", "hello"); // with extension should now fail try testExecError(error.FileNotFound, allocator, "hello.exe"); @@ -91,7 +79,7 @@ pub fn main() anyerror!void { try testExec(allocator, "heLLo", "hello from exe\n"); try tmp.dir.makeDir("something"); - try tmp.dir.rename("hello", "something/hello.exe"); + try renameExe(tmp.dir, "hello", "something/hello.exe"); const relative_path_no_ext = try std.fs.path.join(allocator, &.{ tmp_relative_path, "something/hello" }); defer allocator.free(relative_path_no_ext); @@ -118,14 +106,14 @@ pub fn main() anyerror!void { try testExecError(error.InvalidExe, allocator, "hello"); // If we now rename hello.exe to have no extension, it will behave differently - try tmp.dir.rename("hello.exe", "hello"); + try renameExe(tmp.dir, "hello.exe", "hello"); // Now, trying to execute it without an extension should treat InvalidExe as recoverable // and skip over it and find hello.bat and execute that try testExec(allocator, "hello", "hello from bat\r\n"); // If we rename the invalid exe to something else - try tmp.dir.rename("hello", "goodbye"); + try renameExe(tmp.dir, "hello", "goodbye"); // Then we should now get FileNotFound when trying to execute 'goodbye', // since that is what the original error will be after searching for 'goodbye' // in the cwd. It will try to execute 'goodbye' from the PATH but the InvalidExe error @@ -151,7 +139,7 @@ pub fn main() anyerror!void { try testExec(allocator, "hello", "hello from bat\r\n"); // If we rename something/hello.exe to something/goodbye.exe - try tmp.dir.rename("something/hello.exe", "something/goodbye.exe"); + try renameExe(tmp.dir, "something/hello.exe", "something/goodbye.exe"); // And try to execute goodbye, then the one in something should be found // since the one in cwd is an invalid executable try testExec(allocator, "goodbye", "hello from exe\n"); @@ -196,7 +184,7 @@ pub fn main() anyerror!void { var subdir_cwd = try tmp.dir.openDir(denormed_something_subdir_wtf8, .{}); defer subdir_cwd.close(); - try tmp.dir.rename("something/goodbye.exe", "hello.exe"); + try renameExe(tmp.dir, "something/goodbye.exe", "hello.exe"); try subdir_cwd.setAsCwd(); // clear the PATH again @@ -229,3 +217,17 @@ fn testExecWithCwd(allocator: std.mem.Allocator, command: []const u8, cwd: ?[]co try std.testing.expectEqualStrings("", result.stderr); try std.testing.expectEqualStrings(expected_stdout, result.stdout); } + +fn renameExe(dir: std.fs.Dir, old_sub_path: []const u8, new_sub_path: []const u8) !void { + var attempt: u5 = 0; + while (true) break dir.rename(old_sub_path, new_sub_path) catch |err| switch (err) { + error.AccessDenied => { + if (attempt == 13) return error.AccessDenied; + // give the kernel a chance to finish closing the executable handle + std.os.windows.kernel32.Sleep(@as(u32, 1) << attempt >> 1); + attempt += 1; + continue; + }, + else => |e| return e, + }; +}