| 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,24 +379,30 @@ pub const File = struct { |
| 379 | if (base.file != null) return; | 379 | if (base.file != null) return; |
| 380 | const emit = base.options.emit orelse return; | 380 | const emit = base.options.emit orelse return; |
| 381 | if (base.child_pid) |pid| { | 381 | if (base.child_pid) |pid| { |
| 382 | // If we try to open the output file in write mode while it is running, | 382 | if (builtin.os.tag == .windows) { |
| 383 | // it will return ETXTBSY. So instead, we copy the file, atomically rename it | 383 | base.cast(Coff).?.ptraceAttach(pid) catch |err| { |
| 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| { | ||
| 396 | log.warn("attaching failed with error: {s}", .{@errorName(err)}); | 384 | log.warn("attaching failed with error: {s}", .{@errorName(err)}); |
| 397 | }, | 385 | }; |
| 398 | .windows => {}, | 386 | } else { |
| 399 | else => return error.HotSwapUnavailableOnHostOperatingSystem, | 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 | base.file = try emit.directory.handle.createFile(emit.sub_path, .{ | 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,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 | fn freeAtom(self: *Coff, atom_index: Atom.Index) void { | 835 | fn freeAtom(self: *Coff, atom_index: Atom.Index) void { |
| 831 | log.debug("freeAtom {d}", .{atom_index}); | 836 | log.debug("freeAtom {d}", .{atom_index}); |
| 832 | 837 |
src/main.zig+19-5| ... | @@ -3817,11 +3817,25 @@ fn runOrTestHotSwap( | ... | @@ -3817,11 +3817,25 @@ fn runOrTestHotSwap( |
| 3817 | runtime_args_start: ?usize, | 3817 | runtime_args_start: ?usize, |
| 3818 | ) !std.ChildProcess.Id { | 3818 | ) !std.ChildProcess.Id { |
| 3819 | const exe_emit = comp.bin_file.options.emit.?; | 3819 | const exe_emit = comp.bin_file.options.emit.?; |
| 3820 | // A naive `directory.join` here will indeed get the correct path to the binary, | 3820 | |
| 3821 | // however, in the case of cwd, we actually want `./foo` so that the path can be executed. | 3821 | const exe_path = switch (builtin.target.os.tag) { |
| 3822 | const exe_path = try fs.path.join(gpa, &[_][]const u8{ | 3822 | // On Windows it seems impossible to perform an atomic rename of a file that is currently |
| 3823 | exe_emit.directory.path orelse ".", exe_emit.sub_path, | 3823 | // running in a process. Therefore, we do the opposite. We create a copy of the file in |
| 3824 | }); | 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 | defer gpa.free(exe_path); | 3839 | defer gpa.free(exe_path); |
| 3826 | 3840 | ||
| 3827 | var argv = std.ArrayList([]const u8).init(gpa); | 3841 | var argv = std.ArrayList([]const u8).init(gpa); |