Skip to content

Commit c6351c7

Browse files
committed
Refactoring
1 parent 20f6ff6 commit c6351c7

File tree

2 files changed

+91
-48
lines changed

2 files changed

+91
-48
lines changed

src/Compilation.zig

Lines changed: 46 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3125,50 +3125,54 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
31253125
const bin_digest = man.finalBin();
31263126
const hex_digest = Cache.binToHex(bin_digest);
31273127

3128-
// Work around windows `AccessDenied` if any files within this
3129-
// directory are open by closing and reopening the file handles.
3130-
// While this need is only documented on windows, there are some
3131-
// niche scenarios, such as WSL on ReFS, where it may be required
3132-
// on other platforms. As the workaround is low-cost, just
3133-
// use it on all platforms rather than trying to isolate every
3134-
// specific case where it's needed.
3135-
const need_writable_dance: enum { no, lf_only, lf_and_debug } = w: {
3136-
if (comp.bin_file) |lf| {
3137-
// We cannot just call `makeExecutable` as it makes a false
3138-
// assumption that we have a file handle open only when linking
3139-
// an executable file. This used to be true when our linkers
3140-
// were incapable of emitting relocatables and static archive.
3141-
// Now that they are capable, we need to unconditionally close
3142-
// the file handle and re-open it in the follow up call to
3143-
// `makeWritable`.
3144-
if (lf.file) |f| {
3145-
f.close();
3146-
lf.file = null;
3147-
3148-
if (lf.closeDebugInfo()) break :w .lf_and_debug;
3149-
break :w .lf_only;
3150-
}
3151-
}
3152-
break :w .no;
3153-
};
3154-
3155-
// Rename the temporary directory into place.
3156-
// Close tmp dir and link.File to avoid open handle during rename.
31573128
whole.tmp_artifact_directory.?.handle.close();
31583129
whole.tmp_artifact_directory = null;
31593130
const s = fs.path.sep_str;
31603131
const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int);
31613132
const o_sub_path = "o" ++ s ++ hex_digest;
3162-
renameTmpIntoCache(comp.dirs.local_cache, tmp_dir_sub_path, o_sub_path) catch |err| {
3163-
return comp.setMiscFailure(
3164-
.rename_results,
3165-
"failed to rename compilation results ('{f}{s}') into local cache ('{f}{s}'): {t}",
3166-
.{
3167-
comp.dirs.local_cache, tmp_dir_sub_path,
3168-
comp.dirs.local_cache, o_sub_path,
3169-
err,
3170-
},
3171-
);
3133+
3134+
// Work around situations (host OS or filesystem) that disallow
3135+
// renaming a directory if any files within have open handles.
3136+
// This is only guaranteed required on windows, but has been
3137+
// observed in one other niche environment (ReFS on WSL).
3138+
const need_writable_dance: link.File.ClosedFiles = dance: {
3139+
// The workaround is only guaranteed required on windows, so try
3140+
// just doing the rename straight up first on other platforms.
3141+
if (builtin.os.tag != .windows) b: {
3142+
renameTmpIntoCache(comp.dirs.local_cache, tmp_dir_sub_path, o_sub_path) catch |err| {
3143+
// If we get AccessDenied, assume the host needs the close/open dance and try again.
3144+
if (err == error.AccessDenied) {
3145+
break :b;
3146+
}
3147+
return comp.setMiscFailure(
3148+
.rename_results,
3149+
"failed to rename compilation results ('{f}{s}') into local cache ('{f}{s}'): {t}",
3150+
.{
3151+
comp.dirs.local_cache, tmp_dir_sub_path,
3152+
comp.dirs.local_cache, o_sub_path,
3153+
err,
3154+
},
3155+
);
3156+
};
3157+
break :dance .no;
3158+
}
3159+
3160+
std.debug.print("trying windows AccessDenied workaround\n", .{});
3161+
3162+
const d = if (comp.bin_file) |f| f.closeBin() else .no;
3163+
renameTmpIntoCache(comp.dirs.local_cache, tmp_dir_sub_path, o_sub_path) catch |err| {
3164+
// If we get AccessDenied here, its not the issue we're working around, so we abort.
3165+
return comp.setMiscFailure(
3166+
.rename_results,
3167+
"failed to rename compilation results ('{f}{s}') into local cache ('{f}{s}'): {t}",
3168+
.{
3169+
comp.dirs.local_cache, tmp_dir_sub_path,
3170+
comp.dirs.local_cache, o_sub_path,
3171+
err,
3172+
},
3173+
);
3174+
};
3175+
break :dance d;
31723176
};
31733177
comp.digest = bin_digest;
31743178

@@ -3180,15 +3184,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
31803184
.root_dir = comp.dirs.local_cache,
31813185
.sub_path = try fs.path.join(arena, &.{ o_sub_path, comp.emit_bin.? }),
31823186
};
3183-
const result: (link.File.OpenError || error{ HotSwapUnavailableOnHostOperatingSystem, RenameAcrossMountPoints, InvalidFileName })!void = switch (need_writable_dance) {
3184-
.no => {},
3185-
.lf_only => lf.makeWritable(),
3186-
.lf_and_debug => res: {
3187-
lf.makeWritable() catch |err| break :res err;
3188-
lf.reopenDebugInfo() catch |err| break :res err;
3189-
},
3190-
};
3191-
result catch |err| {
3187+
// reopenBin will call makeWritable for us, as well as any
3188+
// extra work needed beyond that function for certain backends
3189+
lf.reopenBin(need_writable_dance) catch |err| {
31923190
return comp.setMiscFailure(
31933191
.rename_results,
31943192
"failed to re-open renamed compilation results ('{f}{s}'): {t}",

src/link.zig

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,51 @@ pub const File = struct {
651651
}
652652
}
653653

654+
pub const ClosedFiles = enum { lf_only, lf_and_debug, no };
655+
656+
/// We might need to temporarily close the output binary when moving the compilation result
657+
/// directory due to the host OS or filesystem not allowing moving a file/directory while a
658+
/// handle remains open.
659+
/// Returns `true` if a file was closed. In that case, `reopenBin` may be called.
660+
pub fn closeBin(base: *File) ClosedFiles {
661+
const f = base.file orelse return .no;
662+
switch (base.tag) {
663+
.elf2, .coff2 => {
664+
const mf = if (base.cast(.elf2)) |elf|
665+
&elf.mf
666+
else if (base.cast(.coff2)) |coff|
667+
&coff.mf
668+
else
669+
unreachable;
670+
mf.unmap();
671+
},
672+
}
673+
f.close();
674+
base.file = null;
675+
if (base.closeDebugInfo()) return .lf_and_debug;
676+
return .lf_only;
677+
}
678+
679+
pub fn reopenBin(base: *File, what: ClosedFiles) !void {
680+
b: switch (what) {
681+
.no => {},
682+
.lf_and_debug => {
683+
try base.reopenDebugInfo();
684+
continue :b .lf_only;
685+
},
686+
.lf_only => {
687+
try base.makeWritable();
688+
switch (base.tag) {
689+
.c, .spirv => {
690+
const emit = base.emit;
691+
base.file = try emit.root_dir.handle.openFile(emit.sub_path, .{ .mode = .read_write });
692+
},
693+
else => {},
694+
}
695+
},
696+
}
697+
}
698+
654699
/// Some linkers create a separate file for debug info, which we might need to temporarily close
655700
/// when moving the compilation result directory due to the host OS not allowing moving a
656701
/// file/directory while a handle remains open.

0 commit comments

Comments
 (0)