windows: workaround kernel race condition in more places

This commit is contained in:
Jacob Young 2025-10-14 23:16:26 -04:00
parent 595cd8935a
commit cc3c4d1069

View File

@ -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,
};
}