aboutsummaryrefslogtreecommitdiff
path: root/lib/std/Build/Cache.zig
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std/Build/Cache.zig')
-rw-r--r--lib/std/Build/Cache.zig154
1 files changed, 123 insertions, 31 deletions
diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig
index 3f96c5f93c..13c08f93a1 100644
--- a/lib/std/Build/Cache.zig
+++ b/lib/std/Build/Cache.zig
@@ -40,7 +40,7 @@ pub fn addPrefix(cache: *Cache, directory: Directory) void {
/// Be sure to call `Manifest.deinit` after successful initialization.
pub fn obtain(cache: *Cache) Manifest {
- return Manifest{
+ return .{
.cache = cache,
.hash = cache.hash,
.manifest_file = null,
@@ -99,9 +99,9 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
}
fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {
- const relative = try std.fs.path.relative(allocator, prefix, path);
+ const relative = try fs.path.relative(allocator, prefix, path);
errdefer allocator.free(relative);
- var component_iterator = std.fs.path.NativeComponentIterator.init(relative) catch {
+ var component_iterator = fs.path.NativeComponentIterator.init(relative) catch {
return error.NotASubPath;
};
if (component_iterator.root() != null) {
@@ -327,13 +327,27 @@ pub const Manifest = struct {
want_refresh_timestamp: bool = true,
files: Files = .{},
hex_digest: HexDigest,
- /// Populated when hit() returns an error because of one
- /// of the files listed in the manifest.
- failed_file_index: ?usize = null,
+ diagnostic: Diagnostic = .none,
/// Keeps track of the last time we performed a file system write to observe
/// what time the file system thinks it is, according to its own granularity.
recent_problematic_timestamp: i128 = 0,
+ pub const Diagnostic = union(enum) {
+ none,
+ manifest_create: fs.File.OpenError,
+ manifest_read: fs.File.ReadError,
+ manifest_lock: fs.File.LockError,
+ file_open: FileOp,
+ file_stat: FileOp,
+ file_read: FileOp,
+ file_hash: FileOp,
+
+ pub const FileOp = struct {
+ file_index: usize,
+ err: anyerror,
+ };
+ };
+
pub const Files = std.ArrayHashMapUnmanaged(File, void, FilesContext, false);
pub const FilesContext = struct {
@@ -452,6 +466,15 @@ pub const Manifest = struct {
return self.addDepFileMaybePost(dir, dep_file_basename);
}
+ pub const HitError = error{
+ /// Unable to check the cache for a reason that has been recorded into
+ /// the `diagnostic` field.
+ CacheCheckFailed,
+ /// A cache manifest file exists however it could not be parsed.
+ InvalidFormat,
+ OutOfMemory,
+ };
+
/// Check the cache to see if the input exists in it. If it exists, returns `true`.
/// A hex encoding of its hash is available by calling `final`.
///
@@ -464,11 +487,11 @@ pub const Manifest = struct {
/// The lock on the manifest file is released when `deinit` is called. As another
/// option, one may call `toOwnedLock` to obtain a smaller object which can represent
/// the lock. `deinit` is safe to call whether or not `toOwnedLock` has been called.
- pub fn hit(self: *Manifest) !bool {
+ pub fn hit(self: *Manifest) HitError!bool {
const gpa = self.cache.gpa;
assert(self.manifest_file == null);
- self.failed_file_index = null;
+ self.diagnostic = .none;
const ext = ".txt";
var manifest_file_path: [hex_digest_len + ext.len]u8 = undefined;
@@ -496,17 +519,56 @@ pub const Manifest = struct {
break;
} else |err| switch (err) {
error.WouldBlock => {
- self.manifest_file = try self.cache.manifest_dir.openFile(&manifest_file_path, .{
+ self.manifest_file = self.cache.manifest_dir.openFile(&manifest_file_path, .{
.mode = .read_write,
.lock = .shared,
- });
+ }) catch |e| {
+ self.diagnostic = .{ .manifest_create = e };
+ return error.CacheCheckFailed;
+ };
break;
},
- // There are no dir components, so you would think that this was
- // unreachable, however we have observed on macOS two processes racing
- // to do openat() with O_CREAT manifest in ENOENT.
- error.FileNotFound => continue,
- else => |e| return e,
+ error.FileNotFound => {
+ // There are no dir components, so the only possibility
+ // should be that the directory behind the handle has been
+ // deleted, however we have observed on macOS two processes
+ // racing to do openat() with O_CREAT manifest in ENOENT.
+ //
+ // As a workaround, we retry with exclusive=true which
+ // disambiguates by returning EEXIST, indicating original
+ // failure was a race, or ENOENT, indicating deletion of
+ // the directory of our open handle.
+ if (builtin.os.tag != .macos) {
+ self.diagnostic = .{ .manifest_create = error.FileNotFound };
+ return error.CacheCheckFailed;
+ }
+
+ if (self.cache.manifest_dir.createFile(&manifest_file_path, .{
+ .read = true,
+ .truncate = false,
+ .lock = .exclusive,
+ .lock_nonblocking = self.want_shared_lock,
+ .exclusive = true,
+ })) |manifest_file| {
+ self.manifest_file = manifest_file;
+ self.have_exclusive_lock = true;
+ break;
+ } else |excl_err| switch (excl_err) {
+ error.WouldBlock, error.PathAlreadyExists => continue,
+ error.FileNotFound => {
+ self.diagnostic = .{ .manifest_create = error.FileNotFound };
+ return error.CacheCheckFailed;
+ },
+ else => |e| {
+ self.diagnostic = .{ .manifest_create = e };
+ return error.CacheCheckFailed;
+ },
+ }
+ },
+ else => |e| {
+ self.diagnostic = .{ .manifest_create = e };
+ return error.CacheCheckFailed;
+ },
}
}
@@ -514,7 +576,14 @@ pub const Manifest = struct {
const input_file_count = self.files.entries.len;
while (true) : (self.unhit(bin_digest, input_file_count)) {
- const file_contents = try self.manifest_file.?.reader().readAllAlloc(gpa, manifest_file_size_max);
+ const file_contents = self.manifest_file.?.reader().readAllAlloc(gpa, manifest_file_size_max) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ error.StreamTooLong => return error.OutOfMemory,
+ else => |e| {
+ self.diagnostic = .{ .manifest_read = e };
+ return error.CacheCheckFailed;
+ },
+ };
defer gpa.free(file_contents);
var any_file_changed = false;
@@ -526,8 +595,11 @@ pub const Manifest = struct {
while (idx < input_file_count) : (idx += 1) {
const ch_file = &self.files.keys()[idx];
self.populateFileHash(ch_file) catch |err| {
- self.failed_file_index = idx;
- return err;
+ self.diagnostic = .{ .file_hash = .{
+ .file_index = idx,
+ .err = err,
+ } };
+ return error.CacheCheckFailed;
};
}
return false;
@@ -605,13 +677,22 @@ pub const Manifest = struct {
if (try self.upgradeToExclusiveLock()) continue;
return false;
},
- else => return error.CacheUnavailable,
+ else => |e| {
+ self.diagnostic = .{ .file_open = .{
+ .file_index = idx,
+ .err = e,
+ } };
+ return error.CacheCheckFailed;
+ },
};
defer this_file.close();
const actual_stat = this_file.stat() catch |err| {
- self.failed_file_index = idx;
- return err;
+ self.diagnostic = .{ .file_stat = .{
+ .file_index = idx,
+ .err = err,
+ } };
+ return error.CacheCheckFailed;
};
const size_match = actual_stat.size == cache_hash_file.stat.size;
const mtime_match = actual_stat.mtime == cache_hash_file.stat.mtime;
@@ -634,8 +715,11 @@ pub const Manifest = struct {
var actual_digest: BinDigest = undefined;
hashFile(this_file, &actual_digest) catch |err| {
- self.failed_file_index = idx;
- return err;
+ self.diagnostic = .{ .file_read = .{
+ .file_index = idx,
+ .err = err,
+ } };
+ return error.CacheCheckFailed;
};
if (!mem.eql(u8, &cache_hash_file.bin_digest, &actual_digest)) {
@@ -662,17 +746,22 @@ pub const Manifest = struct {
if (try self.upgradeToExclusiveLock()) continue;
self.manifest_dirty = true;
while (idx < input_file_count) : (idx += 1) {
- const ch_file = &self.files.keys()[idx];
- self.populateFileHash(ch_file) catch |err| {
- self.failed_file_index = idx;
- return err;
+ self.populateFileHash(&self.files.keys()[idx]) catch |err| {
+ self.diagnostic = .{ .file_hash = .{
+ .file_index = idx,
+ .err = err,
+ } };
+ return error.CacheCheckFailed;
};
}
return false;
}
if (self.want_shared_lock) {
- try self.downgradeToSharedLock();
+ self.downgradeToSharedLock() catch |err| {
+ self.diagnostic = .{ .manifest_lock = err };
+ return error.CacheCheckFailed;
+ };
}
return true;
@@ -1010,7 +1099,7 @@ pub const Manifest = struct {
self.have_exclusive_lock = false;
}
- fn upgradeToExclusiveLock(self: *Manifest) !bool {
+ fn upgradeToExclusiveLock(self: *Manifest) error{CacheCheckFailed}!bool {
if (self.have_exclusive_lock) return false;
assert(self.manifest_file != null);
@@ -1022,7 +1111,10 @@ pub const Manifest = struct {
// Here we intentionally have a period where the lock is released, in case there are
// other processes holding a shared lock.
manifest_file.unlock();
- try manifest_file.lock(.exclusive);
+ manifest_file.lock(.exclusive) catch |err| {
+ self.diagnostic = .{ .manifest_lock = err };
+ return error.CacheCheckFailed;
+ };
}
self.have_exclusive_lock = true;
return true;
@@ -1132,7 +1224,7 @@ pub fn writeSmallFile(dir: fs.Dir, sub_path: []const u8, data: []const u8) !void
}
}
-fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) !void {
+fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadError!void {
var buf: [1024]u8 = undefined;
var hasher = hasher_init;
var off: u64 = 0;