Commit b2bc6073c8

Jacob Young <jacobly0@users.noreply.github.com>
2025-10-09 21:22:14
windows: workaround kernel race condition
This was causing flaky CI failures.
1 parent c17e186
Changed files (3)
lib
src
test
standalone
windows_spawn
lib/std/os/windows.zig
@@ -1912,6 +1912,7 @@ pub const CreateProcessError = error{
     NameTooLong,
     InvalidExe,
     SystemResources,
+    FileBusy,
     Unexpected,
 };
 
@@ -1982,6 +1983,7 @@ pub fn CreateProcessW(
             .INVALID_PARAMETER => unreachable,
             .INVALID_NAME => return error.InvalidName,
             .FILENAME_EXCED_RANGE => return error.NameTooLong,
+            .SHARING_VIOLATION => return error.FileBusy,
             // These are all the system errors that are mapped to ENOEXEC by
             // the undocumented _dosmaperr (old CRT) or __acrt_errno_map_os_error
             // (newer CRT) functions. Their code can be found in crt/src/dosmap.c (old SDK)
src/link.zig
@@ -616,9 +616,19 @@ pub const File = struct {
                     &coff.mf
                 else
                     unreachable;
-                mf.file = try base.emit.root_dir.handle.openFile(base.emit.sub_path, .{
+                mf.file = for (0..2) |_| break base.emit.root_dir.handle.openFile(base.emit.sub_path, .{
                     .mode = .read_write,
-                });
+                }) catch |err| switch (err) {
+                    error.AccessDenied => switch (builtin.os.tag) {
+                        .windows => {
+                            // give the kernel a chance to finish closing the executable handle
+                            std.os.windows.kernel32.Sleep(0);
+                            continue;
+                        },
+                        else => return error.AccessDenied,
+                    },
+                    else => |e| return e,
+                } else return error.AccessDenied;
                 base.file = mf.file;
                 try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1]));
             },
test/standalone/windows_spawn/main.zig
@@ -71,7 +71,14 @@ pub fn main() anyerror!void {
     try testExec(allocator, "heLLo", "hello from exe\n");
 
     // now rename the exe to not have an extension
-    try tmp.dir.rename("hello.exe", "hello");
+    for (0..2) |_| break tmp.dir.rename("hello.exe", "hello") catch |err| switch (err) {
+        error.AccessDenied => {
+            // give the kernel a chance to finish closing the executable handle
+            std.os.windows.kernel32.Sleep(0);
+            continue;
+        },
+        else => |e| return e,
+    } else return error.AccessDenied;
 
     // with extension should now fail
     try testExecError(error.FileNotFound, allocator, "hello.exe");