From 274555be21ea756d8480a586f771274a79a58d80 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Dec 2021 16:38:12 -0700 Subject: stage2: improve handling of the generated file builtin.zig All Zig code is eligible to `@import("builtin")` which is mapped to a generated file, build.zig, based on the target and other settings. Zig invocations which share the same target settings will generate the same builtin.zig file and thus the path to builtin.zig is in a shared cache folder, and different projects can sometimes use the same file. Before this commit, this led to race conditions where multiple invocations of `zig` would race to write this file. If one process wanted to *read* the file while the other process *wrote* the file, the reading process could observe a truncated or partially written builtin.zig file. This commit makes the following improvements: - limitations: - avoid clobbering the inode, mtime in the hot path - avoid creating a partially written file - builtin.zig needs to be on disk for debug info / stack trace purposes - don't mark the task as complete until the file is finished being populated (possibly by an external process) - strategy: - create the `@import("builtin")` `Module.File` during the AstGen work, based on generating the contents in memory rather than loading from disk. - write builtin.zig in a separate task that doesn't have to complete until the end of the AstGen work queue so that it can be done in parallel with everything else. - when writing the file, first stat the file path. If it exists, we are done. - otherwise, write the file to a temp file in the same directory and atomically rename it into place (clobbering the inode, mtime in the cold path). - summary: - all limitations respected - hot path: one stat() syscall that happens in a worker thread This required adding a missing function to the standard library: `std.fs.Dir.statFile`. In this commit, it does open() and then fstat() which is two syscalls. It should be improved in a future commit to only make one. Fixes #9439. --- src/Module.zig | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) (limited to 'src/Module.zig') diff --git a/src/Module.zig b/src/Module.zig index b8e5867775..341b6bb56e 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2967,6 +2967,78 @@ fn updateZirRefs(gpa: Allocator, file: *File, old_zir: Zir) !void { } } +pub fn populateBuiltinFile(mod: *Module) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const comp = mod.comp; + const pkg_and_file = blk: { + comp.mutex.lock(); + defer comp.mutex.unlock(); + + const builtin_pkg = mod.main_pkg.table.get("builtin").?; + const result = try mod.importPkg(builtin_pkg); + break :blk .{ + .file = result.file, + .pkg = builtin_pkg, + }; + }; + const file = pkg_and_file.file; + const builtin_pkg = pkg_and_file.pkg; + const gpa = mod.gpa; + file.source = try comp.generateBuiltinZigSource(gpa); + file.source_loaded = true; + + if (builtin_pkg.root_src_directory.handle.statFile(builtin_pkg.root_src_path)) |stat| { + if (stat.size != file.source.len) { + const full_path = try builtin_pkg.root_src_directory.join(gpa, &.{ + builtin_pkg.root_src_path, + }); + defer gpa.free(full_path); + + log.warn( + "the cached file '{s}' had the wrong size. Expected {d}, found {d}. " ++ + "Overwriting with correct file contents now", + .{ full_path, file.source.len, stat.size }, + ); + + try writeBuiltinFile(file, builtin_pkg); + } else { + file.stat_size = stat.size; + file.stat_inode = stat.inode; + file.stat_mtime = stat.mtime; + } + } else |err| switch (err) { + error.BadPathName => unreachable, // it's always "builtin.zig" + error.NameTooLong => unreachable, // it's always "builtin.zig" + error.PipeBusy => unreachable, // it's not a pipe + error.WouldBlock => unreachable, // not asking for non-blocking I/O + + error.FileNotFound => try writeBuiltinFile(file, builtin_pkg), + + else => |e| return e, + } + + file.tree = try std.zig.parse(gpa, file.source); + file.tree_loaded = true; + assert(file.tree.errors.len == 0); // builtin.zig must parse + + file.zir = try AstGen.generate(gpa, file.tree); + file.zir_loaded = true; + file.status = .success_zir; +} + +pub fn writeBuiltinFile(file: *File, builtin_pkg: *Package) !void { + var af = try builtin_pkg.root_src_directory.handle.atomicFile(builtin_pkg.root_src_path, .{}); + defer af.deinit(); + try af.file.writeAll(file.source); + try af.finish(); + + file.stat_size = file.source.len; + file.stat_inode = 0; // dummy value + file.stat_mtime = 0; // dummy value +} + pub fn mapOldZirToNew( gpa: Allocator, old_zir: Zir, -- cgit v1.2.3