| author | |
| committer | |
| log | 22e1b033607580756329879ba7158a29aca57981 |
| tree | d7f8a911014b64418e7a4058735c4c430f748893 |
| parent | 64214b1ca6a295af8521ed6fb3be5a3244a42564 |
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.3 files changed, 47 insertions(+), 22 deletions(-)
src/link.zig+23-17| ... | ... | @@ -379,24 +379,30 @@ pub const File = struct { |
| 379 | 379 | if (base.file != null) return; |
| 380 | 380 | const emit = base.options.emit orelse return; |
| 381 | 381 | if (base.child_pid) |pid| { |
| 382 | // If we try to open the output file in write mode while it is running, | |
| 383 | // it will return ETXTBSY. So instead, we copy the file, atomically rename it | |
| 384 | // over top of the exe path, and then proceed normally. This changes the inode, | |
| 385 | // avoiding the error. | |
| 386 | const tmp_sub_path = try std.fmt.allocPrint(base.allocator, "{s}-{x}", .{ | |
| 387 | emit.sub_path, std.crypto.random.int(u32), | |
| 388 | }); | |
| 389 | try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{}); | |
| 390 | try emit.directory.handle.rename(tmp_sub_path, emit.sub_path); | |
| 391 | switch (builtin.os.tag) { | |
| 392 | .linux => std.os.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| { | |
| 393 | log.warn("ptrace failure: {s}", .{@errorName(err)}); | |
| 394 | }, | |
| 395 | .macos => base.cast(MachO).?.ptraceAttach(pid) catch |err| { | |
| 382 | if (builtin.os.tag == .windows) { | |
| 383 | base.cast(Coff).?.ptraceAttach(pid) catch |err| { | |
| 396 | 384 | log.warn("attaching failed with error: {s}", .{@errorName(err)}); |
| 397 | }, | |
| 398 | .windows => {}, | |
| 399 | else => return error.HotSwapUnavailableOnHostOperatingSystem, | |
| 385 | }; | |
| 386 | } else { | |
| 387 | // If we try to open the output file in write mode while it is running, | |
| 388 | // it will return ETXTBSY. So instead, we copy the file, atomically rename it | |
| 389 | // over top of the exe path, and then proceed normally. This changes the inode, | |
| 390 | // avoiding the error. | |
| 391 | const tmp_sub_path = try std.fmt.allocPrint(base.allocator, "{s}-{x}", .{ | |
| 392 | emit.sub_path, std.crypto.random.int(u32), | |
| 393 | }); | |
| 394 | try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{}); | |
| 395 | try emit.directory.handle.rename(tmp_sub_path, emit.sub_path); | |
| 396 | switch (builtin.os.tag) { | |
| 397 | .linux => std.os.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| { | |
| 398 | log.warn("ptrace failure: {s}", .{@errorName(err)}); | |
| 399 | }, | |
| 400 | .macos => base.cast(MachO).?.ptraceAttach(pid) catch |err| { | |
| 401 | log.warn("attaching failed with error: {s}", .{@errorName(err)}); | |
| 402 | }, | |
| 403 | .windows => unreachable, | |
| 404 | else => return error.HotSwapUnavailableOnHostOperatingSystem, | |
| 405 | } | |
| 400 | 406 | } |
| 401 | 407 | } |
| 402 | 408 | base.file = try emit.directory.handle.createFile(emit.sub_path, .{ |
src/link/Coff.zig+5| ... | ... | @@ -827,6 +827,11 @@ fn resolveRelocs(self: *Coff, atom_index: Atom.Index, code: []u8) void { |
| 827 | 827 | } |
| 828 | 828 | } |
| 829 | 829 | |
| 830 | pub fn ptraceAttach(self: *Coff, handle: std.os.pid_t) !void { | |
| 831 | _ = self; | |
| 832 | log.warn("attaching to process with handle {*}", .{handle}); | |
| 833 | } | |
| 834 | ||
| 830 | 835 | fn freeAtom(self: *Coff, atom_index: Atom.Index) void { |
| 831 | 836 | log.debug("freeAtom {d}", .{atom_index}); |
| 832 | 837 |
src/main.zig+19-5| ... | ... | @@ -3817,11 +3817,25 @@ fn runOrTestHotSwap( |
| 3817 | 3817 | runtime_args_start: ?usize, |
| 3818 | 3818 | ) !std.ChildProcess.Id { |
| 3819 | 3819 | const exe_emit = comp.bin_file.options.emit.?; |
| 3820 | // A naive `directory.join` here will indeed get the correct path to the binary, | |
| 3821 | // however, in the case of cwd, we actually want `./foo` so that the path can be executed. | |
| 3822 | const exe_path = try fs.path.join(gpa, &[_][]const u8{ | |
| 3823 | exe_emit.directory.path orelse ".", exe_emit.sub_path, | |
| 3824 | }); | |
| 3820 | ||
| 3821 | const exe_path = switch (builtin.target.os.tag) { | |
| 3822 | // On Windows it seems impossible to perform an atomic rename of a file that is currently | |
| 3823 | // running in a process. Therefore, we do the opposite. We create a copy of the file in | |
| 3824 | // tmp zig-cache and use it to spawn the child process. This way we are free to update | |
| 3825 | // the binary with each requested hot update. | |
| 3826 | .windows => blk: { | |
| 3827 | try exe_emit.directory.handle.copyFile(exe_emit.sub_path, comp.local_cache_directory.handle, exe_emit.sub_path, .{}); | |
| 3828 | break :blk try fs.path.join(gpa, &[_][]const u8{ | |
| 3829 | comp.local_cache_directory.path orelse ".", exe_emit.sub_path, | |
| 3830 | }); | |
| 3831 | }, | |
| 3832 | ||
| 3833 | // A naive `directory.join` here will indeed get the correct path to the binary, | |
| 3834 | // however, in the case of cwd, we actually want `./foo` so that the path can be executed. | |
| 3835 | else => try fs.path.join(gpa, &[_][]const u8{ | |
| 3836 | exe_emit.directory.path orelse ".", exe_emit.sub_path, | |
| 3837 | }), | |
| 3838 | }; | |
| 3825 | 3839 | defer gpa.free(exe_path); |
| 3826 | 3840 | |
| 3827 | 3841 | var argv = std.ArrayList([]const u8).init(gpa); |