authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-03-30 00:57:53+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-03-30 21:08:30+02:00
log22e1b033607580756329879ba7158a29aca57981
treed7f8a911014b64418e7a4058735c4c430f748893
parent64214b1ca6a295af8521ed6fb3be5a3244a42564

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.

3 files changed, 47 insertions(+), 22 deletions(-)

src/link.zig+23-17
......@@ -379,24 +379,30 @@ pub const File = struct {
379379 if (base.file != null) return;
380380 const emit = base.options.emit orelse return;
381381 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| {
396384 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 }
400406 }
401407 }
402408 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 {
827827 }
828828}
829829
830pub fn ptraceAttach(self: *Coff, handle: std.os.pid_t) !void {
831 _ = self;
832 log.warn("attaching to process with handle {*}", .{handle});
833}
834
830835fn freeAtom(self: *Coff, atom_index: Atom.Index) void {
831836 log.debug("freeAtom {d}", .{atom_index});
832837
src/main.zig+19-5
......@@ -3817,11 +3817,25 @@ fn runOrTestHotSwap(
38173817 runtime_args_start: ?usize,
38183818) !std.ChildProcess.Id {
38193819 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 };
38253839 defer gpa.free(exe_path);
38263840
38273841 var argv = std.ArrayList([]const u8).init(gpa);