From 22e1b033607580756329879ba7158a29aca57981 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 30 Mar 2023 00:57:53 +0200 Subject: [PATCH] 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. --- src/link.zig | 40 +++++++++++++++++++++++----------------- src/link/Coff.zig | 5 +++++ src/main.zig | 24 +++++++++++++++++++----- 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/link.zig b/src/link.zig index 239dc646b2ce3bbbb96079e29cccec8c5e878daf..d8210271961ba6c790b6936413e9daf04e6aacc0 100644 --- a/src/link.zig +++ b/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, .{ diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 7b5539287d487ba9c5665b529a97638be3c7ec43..240c61bffd05ae2f99267c3eb09a6d0cb98bfb98 100644 --- a/src/link/Coff.zig +++ b/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}); diff --git a/src/main.zig b/src/main.zig index c96fd2576680b8ca928f8e87752ccf932cf60e87..1a445107ba20e86440f9ca50f20e7f25f49dddfd 100644 --- a/src/main.zig +++ b/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); -- 2.54.0