Commit 22e1b03360

Jakub Konka <kubkon@jakubkonka.com>
2023-03-30 00:57:53
coff: use copy in zig-cache for child process in HCS
Ideally, we would just do an atomic rename, but so far I had no luck. I have also tried marking the file to delete-on-close but then we cannot use it to spawn the process. So for now, let's just put a copy in `zig-cache` and let the user decide when to recycle the cache dir.
1 parent 64214b1
Changed files (3)
src/link/Coff.zig
@@ -827,6 +827,11 @@ fn resolveRelocs(self: *Coff, atom_index: Atom.Index, code: []u8) void {
     }
 }
 
+pub fn ptraceAttach(self: *Coff, handle: std.os.pid_t) !void {
+    _ = self;
+    log.warn("attaching to process with handle {*}", .{handle});
+}
+
 fn freeAtom(self: *Coff, atom_index: Atom.Index) void {
     log.debug("freeAtom {d}", .{atom_index});
 
src/link.zig
@@ -379,24 +379,30 @@ pub const File = struct {
                 if (base.file != null) return;
                 const emit = base.options.emit orelse return;
                 if (base.child_pid) |pid| {
-                    // If we try to open the output file in write mode while it is running,
-                    // it will return ETXTBSY. So instead, we copy the file, atomically rename it
-                    // over top of the exe path, and then proceed normally. This changes the inode,
-                    // avoiding the error.
-                    const tmp_sub_path = try std.fmt.allocPrint(base.allocator, "{s}-{x}", .{
-                        emit.sub_path, std.crypto.random.int(u32),
-                    });
-                    try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{});
-                    try emit.directory.handle.rename(tmp_sub_path, emit.sub_path);
-                    switch (builtin.os.tag) {
-                        .linux => std.os.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| {
-                            log.warn("ptrace failure: {s}", .{@errorName(err)});
-                        },
-                        .macos => base.cast(MachO).?.ptraceAttach(pid) catch |err| {
+                    if (builtin.os.tag == .windows) {
+                        base.cast(Coff).?.ptraceAttach(pid) catch |err| {
                             log.warn("attaching failed with error: {s}", .{@errorName(err)});
-                        },
-                        .windows => {},
-                        else => return error.HotSwapUnavailableOnHostOperatingSystem,
+                        };
+                    } else {
+                        // If we try to open the output file in write mode while it is running,
+                        // it will return ETXTBSY. So instead, we copy the file, atomically rename it
+                        // over top of the exe path, and then proceed normally. This changes the inode,
+                        // avoiding the error.
+                        const tmp_sub_path = try std.fmt.allocPrint(base.allocator, "{s}-{x}", .{
+                            emit.sub_path, std.crypto.random.int(u32),
+                        });
+                        try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{});
+                        try emit.directory.handle.rename(tmp_sub_path, emit.sub_path);
+                        switch (builtin.os.tag) {
+                            .linux => std.os.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| {
+                                log.warn("ptrace failure: {s}", .{@errorName(err)});
+                            },
+                            .macos => base.cast(MachO).?.ptraceAttach(pid) catch |err| {
+                                log.warn("attaching failed with error: {s}", .{@errorName(err)});
+                            },
+                            .windows => unreachable,
+                            else => return error.HotSwapUnavailableOnHostOperatingSystem,
+                        }
                     }
                 }
                 base.file = try emit.directory.handle.createFile(emit.sub_path, .{
src/main.zig
@@ -3817,11 +3817,25 @@ fn runOrTestHotSwap(
     runtime_args_start: ?usize,
 ) !std.ChildProcess.Id {
     const exe_emit = comp.bin_file.options.emit.?;
-    // A naive `directory.join` here will indeed get the correct path to the binary,
-    // however, in the case of cwd, we actually want `./foo` so that the path can be executed.
-    const exe_path = try fs.path.join(gpa, &[_][]const u8{
-        exe_emit.directory.path orelse ".", exe_emit.sub_path,
-    });
+
+    const exe_path = switch (builtin.target.os.tag) {
+        // On Windows it seems impossible to perform an atomic rename of a file that is currently
+        // running in a process. Therefore, we do the opposite. We create a copy of the file in
+        // tmp zig-cache and use it to spawn the child process. This way we are free to update
+        // the binary with each requested hot update.
+        .windows => blk: {
+            try exe_emit.directory.handle.copyFile(exe_emit.sub_path, comp.local_cache_directory.handle, exe_emit.sub_path, .{});
+            break :blk try fs.path.join(gpa, &[_][]const u8{
+                comp.local_cache_directory.path orelse ".", exe_emit.sub_path,
+            });
+        },
+
+        // A naive `directory.join` here will indeed get the correct path to the binary,
+        // however, in the case of cwd, we actually want `./foo` so that the path can be executed.
+        else => try fs.path.join(gpa, &[_][]const u8{
+            exe_emit.directory.path orelse ".", exe_emit.sub_path,
+        }),
+    };
     defer gpa.free(exe_path);
 
     var argv = std.ArrayList([]const u8).init(gpa);