Function hit [src]

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. This function will also acquire an exclusive lock to the manifest file. This means that a process holding a Manifest will block any other process attempting to acquire the lock. If want_shared_lock is true, a cache hit guarantees the manifest file to be locked in shared mode, and a cache miss guarantees the manifest file to be locked in exclusive mode. 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.

Prototype

pub fn hit(self: *Manifest) HitError!bool

Parameters

self: *Manifest

Possible Errors

CacheCheckFailed

Unable to check the cache for a reason that has been recorded into the diagnostic field.

InvalidFormat

A cache manifest file exists however it could not be parsed.

OutOfMemory

Source

pub fn hit(self: *Manifest) HitError!bool { const gpa = self.cache.gpa; assert(self.manifest_file == null); self.diagnostic = .none; const ext = ".txt"; var manifest_file_path: [hex_digest_len + ext.len]u8 = undefined; var bin_digest: BinDigest = undefined; self.hash.hasher.final(&bin_digest); self.hex_digest = binToHex(bin_digest); self.hash.hasher = hasher_init; self.hash.hasher.update(&bin_digest); @memcpy(manifest_file_path[0..self.hex_digest.len], &self.hex_digest); manifest_file_path[hex_digest_len..][0..ext.len].* = ext.*; while (true) { if (self.cache.manifest_dir.createFile(&manifest_file_path, .{ .read = true, .truncate = false, .lock = .exclusive, .lock_nonblocking = self.want_shared_lock, })) |manifest_file| { self.manifest_file = manifest_file; self.have_exclusive_lock = true; break; } else |err| switch (err) { error.WouldBlock => { 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; }, 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; }, } } self.want_refresh_timestamp = true; const input_file_count = self.files.entries.len; while (true) : (self.unhit(bin_digest, input_file_count)) { 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; var line_iter = mem.tokenizeScalar(u8, file_contents, '\n'); var idx: usize = 0; if (if (line_iter.next()) |line| !std.mem.eql(u8, line, manifest_header) else true) { 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.diagnostic = .{ .file_hash = .{ .file_index = idx, .err = err, } }; return error.CacheCheckFailed; }; } return false; } while (line_iter.next()) |line| { defer idx += 1; var iter = mem.tokenizeScalar(u8, line, ' '); const size = iter.next() orelse return error.InvalidFormat; const inode = iter.next() orelse return error.InvalidFormat; const mtime_nsec_str = iter.next() orelse return error.InvalidFormat; const digest_str = iter.next() orelse return error.InvalidFormat; const prefix_str = iter.next() orelse return error.InvalidFormat; const file_path = iter.rest(); const stat_size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat; const stat_inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat; const stat_mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat; const file_bin_digest = b: { if (digest_str.len != hex_digest_len) return error.InvalidFormat; var bd: BinDigest = undefined; _ = fmt.hexToBytes(&bd, digest_str) catch return error.InvalidFormat; break :b bd; }; const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat; if (prefix >= self.cache.prefixes_len) return error.InvalidFormat; if (file_path.len == 0) return error.InvalidFormat; const cache_hash_file = f: { const prefixed_path: PrefixedPath = .{ .prefix = prefix, .sub_path = file_path, // expires with file_contents }; if (idx < input_file_count) { const file = &self.files.keys()[idx]; if (!file.prefixed_path.eql(prefixed_path)) return error.InvalidFormat; file.stat = .{ .size = stat_size, .inode = stat_inode, .mtime = stat_mtime, }; file.bin_digest = file_bin_digest; break :f file; } const gop = try self.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{}); errdefer _ = self.files.pop(); if (!gop.found_existing) { gop.key_ptr.* = .{ .prefixed_path = .{ .prefix = prefix, .sub_path = try gpa.dupe(u8, file_path), }, .contents = null, .max_file_size = null, .handle = null, .stat = .{ .size = stat_size, .inode = stat_inode, .mtime = stat_mtime, }, .bin_digest = file_bin_digest, }; } break :f gop.key_ptr; }; const pp = cache_hash_file.prefixed_path; const dir = self.cache.prefixes()[pp.prefix].handle; const this_file = dir.openFile(pp.sub_path, .{ .mode = .read_only }) catch |err| switch (err) { error.FileNotFound => { if (try self.upgradeToExclusiveLock()) continue; return false; }, 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.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; const inode_match = actual_stat.inode == cache_hash_file.stat.inode; if (!size_match or !mtime_match or !inode_match) { self.manifest_dirty = true; cache_hash_file.stat = .{ .size = actual_stat.size, .mtime = actual_stat.mtime, .inode = actual_stat.inode, }; if (self.isProblematicTimestamp(cache_hash_file.stat.mtime)) { // The actual file has an unreliable timestamp, force it to be hashed cache_hash_file.stat.mtime = 0; cache_hash_file.stat.inode = 0; } var actual_digest: BinDigest = undefined; hashFile(this_file, &actual_digest) catch |err| { self.diagnostic = .{ .file_read = .{ .file_index = idx, .err = err, } }; return error.CacheCheckFailed; }; if (!mem.eql(u8, &cache_hash_file.bin_digest, &actual_digest)) { cache_hash_file.bin_digest = actual_digest; // keep going until we have the input file digests any_file_changed = true; } } if (!any_file_changed) { self.hash.hasher.update(&cache_hash_file.bin_digest); } } if (any_file_changed) { if (try self.upgradeToExclusiveLock()) continue; // cache miss // keep the manifest file open self.unhit(bin_digest, input_file_count); return false; } if (idx < input_file_count) { if (try self.upgradeToExclusiveLock()) continue; self.manifest_dirty = true; while (idx < input_file_count) : (idx += 1) { 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) { self.downgradeToSharedLock() catch |err| { self.diagnostic = .{ .manifest_lock = err }; return error.CacheCheckFailed; }; } return true; } }