Commit df4c30ca16

Andrew Kelley <andrew@ziglang.org>
2025-10-27 21:21:49
link: move the windows kernel bug workaround to Io implementation
1 parent 0211936
Changed files (2)
lib
src
lib/std/Io/Threaded.zig
@@ -2081,6 +2081,10 @@ fn dirOpenFileWindowsInner(
     const create_file_flags: w.ULONG = file_or_dir_flag |
         if (flags.follow_symlinks) blocking_flag else w.FILE_OPEN_REPARSE_POINT;
 
+    // There are multiple kernel bugs being worked around with retries.
+    const max_attempts = 13;
+    var attempt: u5 = 0;
+
     const handle = while (true) {
         try t.checkCancel();
 
@@ -2110,7 +2114,17 @@ fn dirOpenFileWindowsInner(
             .NO_MEDIA_IN_DEVICE => return error.NoDevice,
             .INVALID_PARAMETER => |err| return w.statusBug(err),
             .SHARING_VIOLATION => return error.AccessDenied,
-            .ACCESS_DENIED => return error.AccessDenied,
+            .ACCESS_DENIED => {
+                // This occurs if the file attempting to be opened is a running
+                // executable. However, there's a kernel bug: the error may be
+                // incorrectly returned for an indeterminate amount of time
+                // after an executable file is closed. Here we work around the
+                // kernel bug with retry attempts.
+                if (attempt - max_attempts == 0) return error.AccessDenied;
+                _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);
+                attempt += 1;
+                continue;
+            },
             .PIPE_BUSY => return error.PipeBusy,
             .PIPE_NOT_AVAILABLE => return error.NoDevice,
             .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err),
@@ -2123,10 +2137,11 @@ fn dirOpenFileWindowsInner(
                 // This error means that there *was* a file in this location on
                 // the file system, but it was deleted. However, the OS is not
                 // finished with the deletion operation, and so this CreateFile
-                // call has failed. There is not really a sane way to handle
-                // this other than retrying the creation after the OS finishes
-                // the deletion.
-                _ = w.kernel32.SleepEx(1, w.FALSE);
+                // call has failed. Here, we simulate the kernel bug being
+                // fixed by sleeping and retrying until the error goes away.
+                if (attempt - max_attempts == 0) return error.AccessDenied;
+                _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);
+                attempt += 1;
                 continue;
             },
             .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
src/link.zig
@@ -1,19 +1,22 @@
-const std = @import("std");
-const build_options = @import("build_options");
 const builtin = @import("builtin");
+const build_options = @import("build_options");
+
+const std = @import("std");
+const Io = std.Io;
 const assert = std.debug.assert;
 const fs = std.fs;
 const mem = std.mem;
 const log = std.log.scoped(.link);
-const trace = @import("tracy.zig").trace;
-const wasi_libc = @import("libs/wasi_libc.zig");
-
 const Allocator = std.mem.Allocator;
 const Cache = std.Build.Cache;
 const Path = std.Build.Cache.Path;
 const Directory = std.Build.Cache.Directory;
 const Compilation = @import("Compilation.zig");
 const LibCInstallation = std.zig.LibCInstallation;
+
+const trace = @import("tracy.zig").trace;
+const wasi_libc = @import("libs/wasi_libc.zig");
+
 const Zcu = @import("Zcu.zig");
 const InternPool = @import("InternPool.zig");
 const Type = @import("Type.zig");
@@ -572,6 +575,7 @@ pub const File = struct {
         dev.check(.make_writable);
         const comp = base.comp;
         const gpa = comp.gpa;
+        const io = comp.io;
         switch (base.tag) {
             .lld => assert(base.file == null),
             .elf, .macho, .wasm, .goff, .xcoff => {
@@ -616,22 +620,9 @@ pub const File = struct {
                     &coff.mf
                 else
                     unreachable;
-                var attempt: u5 = 0;
-                mf.file = while (true) break base.emit.root_dir.handle.openFile(base.emit.sub_path, .{
+                mf.file = .adaptFromNewApi(try Io.Dir.openFile(base.emit.root_dir.handle.adaptToNewApi(), io, base.emit.sub_path, .{
                     .mode = .read_write,
-                }) catch |err| switch (err) {
-                    error.AccessDenied => switch (builtin.os.tag) {
-                        .windows => {
-                            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 => return error.AccessDenied,
-                    },
-                    else => |e| return e,
-                };
+                }));
                 base.file = mf.file;
                 try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1]));
             },