authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2025-10-14 23:16:26-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2025-10-14 23:16:36-04:00
logcc3c4d1069c7b60a5588cb348f9fd927772b069b
tree0e82f7875722d8ad7f238522c2bbe8a432bfa544
parent595cd8935a8a1660518eb35ba4edd92a90e48f71

windows: workaround kernel race condition in more places


1 files changed, 20 insertions(+), 18 deletions(-)

test/standalone/windows_spawn/main.zig+20-18
...@@ -71,19 +71,7 @@ pub fn main() anyerror!void {...@@ -71,19 +71,7 @@ pub fn main() anyerror!void {
71 try testExec(allocator, "heLLo", "hello from exe\n");71 try testExec(allocator, "heLLo", "hello from exe\n");
7272
73 // now rename the exe to not have an extension73 // now rename the exe to not have an extension
74 {74 try renameExe(tmp.dir, "hello.exe", "hello");
75 var attempt: u5 = 0;
76 while (true) break tmp.dir.rename("hello.exe", "hello") catch |err| switch (err) {
77 error.AccessDenied => {
78 if (attempt == 13) return error.AccessDenied;
79 // give the kernel a chance to finish closing the executable handle
80 std.os.windows.kernel32.Sleep(@as(u32, 1) << attempt >> 1);
81 attempt += 1;
82 continue;
83 },
84 else => |e| return e,
85 };
86 }
8775
88 // with extension should now fail76 // with extension should now fail
89 try testExecError(error.FileNotFound, allocator, "hello.exe");77 try testExecError(error.FileNotFound, allocator, "hello.exe");
...@@ -91,7 +79,7 @@ pub fn main() anyerror!void {...@@ -91,7 +79,7 @@ pub fn main() anyerror!void {
91 try testExec(allocator, "heLLo", "hello from exe\n");79 try testExec(allocator, "heLLo", "hello from exe\n");
9280
93 try tmp.dir.makeDir("something");81 try tmp.dir.makeDir("something");
94 try tmp.dir.rename("hello", "something/hello.exe");82 try renameExe(tmp.dir, "hello", "something/hello.exe");
9583
96 const relative_path_no_ext = try std.fs.path.join(allocator, &.{ tmp_relative_path, "something/hello" });84 const relative_path_no_ext = try std.fs.path.join(allocator, &.{ tmp_relative_path, "something/hello" });
97 defer allocator.free(relative_path_no_ext);85 defer allocator.free(relative_path_no_ext);
...@@ -118,14 +106,14 @@ pub fn main() anyerror!void {...@@ -118,14 +106,14 @@ pub fn main() anyerror!void {
118 try testExecError(error.InvalidExe, allocator, "hello");106 try testExecError(error.InvalidExe, allocator, "hello");
119107
120 // If we now rename hello.exe to have no extension, it will behave differently108 // If we now rename hello.exe to have no extension, it will behave differently
121 try tmp.dir.rename("hello.exe", "hello");109 try renameExe(tmp.dir, "hello.exe", "hello");
122110
123 // Now, trying to execute it without an extension should treat InvalidExe as recoverable111 // Now, trying to execute it without an extension should treat InvalidExe as recoverable
124 // and skip over it and find hello.bat and execute that112 // and skip over it and find hello.bat and execute that
125 try testExec(allocator, "hello", "hello from bat\r\n");113 try testExec(allocator, "hello", "hello from bat\r\n");
126114
127 // If we rename the invalid exe to something else115 // If we rename the invalid exe to something else
128 try tmp.dir.rename("hello", "goodbye");116 try renameExe(tmp.dir, "hello", "goodbye");
129 // Then we should now get FileNotFound when trying to execute 'goodbye',117 // Then we should now get FileNotFound when trying to execute 'goodbye',
130 // since that is what the original error will be after searching for 'goodbye'118 // since that is what the original error will be after searching for 'goodbye'
131 // in the cwd. It will try to execute 'goodbye' from the PATH but the InvalidExe error119 // 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 {...@@ -151,7 +139,7 @@ pub fn main() anyerror!void {
151 try testExec(allocator, "hello", "hello from bat\r\n");139 try testExec(allocator, "hello", "hello from bat\r\n");
152140
153 // If we rename something/hello.exe to something/goodbye.exe141 // If we rename something/hello.exe to something/goodbye.exe
154 try tmp.dir.rename("something/hello.exe", "something/goodbye.exe");142 try renameExe(tmp.dir, "something/hello.exe", "something/goodbye.exe");
155 // And try to execute goodbye, then the one in something should be found143 // And try to execute goodbye, then the one in something should be found
156 // since the one in cwd is an invalid executable144 // since the one in cwd is an invalid executable
157 try testExec(allocator, "goodbye", "hello from exe\n");145 try testExec(allocator, "goodbye", "hello from exe\n");
...@@ -196,7 +184,7 @@ pub fn main() anyerror!void {...@@ -196,7 +184,7 @@ pub fn main() anyerror!void {
196 var subdir_cwd = try tmp.dir.openDir(denormed_something_subdir_wtf8, .{});184 var subdir_cwd = try tmp.dir.openDir(denormed_something_subdir_wtf8, .{});
197 defer subdir_cwd.close();185 defer subdir_cwd.close();
198186
199 try tmp.dir.rename("something/goodbye.exe", "hello.exe");187 try renameExe(tmp.dir, "something/goodbye.exe", "hello.exe");
200 try subdir_cwd.setAsCwd();188 try subdir_cwd.setAsCwd();
201189
202 // clear the PATH again190 // clear the PATH again
...@@ -229,3 +217,17 @@ fn testExecWithCwd(allocator: std.mem.Allocator, command: []const u8, cwd: ?[]co...@@ -229,3 +217,17 @@ fn testExecWithCwd(allocator: std.mem.Allocator, command: []const u8, cwd: ?[]co
229 try std.testing.expectEqualStrings("", result.stderr);217 try std.testing.expectEqualStrings("", result.stderr);
230 try std.testing.expectEqualStrings(expected_stdout, result.stdout);218 try std.testing.expectEqualStrings(expected_stdout, result.stdout);
231}219}
220
221fn renameExe(dir: std.fs.Dir, old_sub_path: []const u8, new_sub_path: []const u8) !void {
222 var attempt: u5 = 0;
223 while (true) break dir.rename(old_sub_path, new_sub_path) catch |err| switch (err) {
224 error.AccessDenied => {
225 if (attempt == 13) return error.AccessDenied;
226 // give the kernel a chance to finish closing the executable handle
227 std.os.windows.kernel32.Sleep(@as(u32, 1) << attempt >> 1);
228 attempt += 1;
229 continue;
230 },
231 else => |e| return e,
232 };
233}