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
Unable to check the cache for a reason that has been recorded into
the diagnostic
field.
A cache manifest file exists however it could not be parsed.
Source
pub fn hit(self: *Manifest) HitError!bool {
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);
@memcpy(manifest_file_path[0..self.hex_digest.len], &self.hex_digest);
manifest_file_path[hex_digest_len..][0..ext.len].* = ext.*;
// We'll try to open the cache with an exclusive lock, but if that would block
// and `want_shared_lock` is set, a shared lock might be sufficient, so we'll
// open with a shared lock instead.
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;
// We're going to construct a second hash. Its input will begin with the digest we've
// already computed (`bin_digest`), and then it'll have the digests of each input file,
// including "post" files (see `addFilePost`). If this is a hit, we learn the set of "post"
// files from the manifest on disk. If this is a miss, we'll learn those from future calls
// to `addFilePost` etc. As such, the state of `self.hash.hasher` after this function
// depends on whether this is a hit or a miss.
//
// If we return `true` indicating a cache hit, then `self.hash.hasher` must already include
// the digests of the "post" files, so the caller can call `final`. Otherwise, on a cache
// miss, `self.hash.hasher` will include the digests of all non-"post" files -- that is,
// the ones we've already been told about. The rest will be discovered through calls to
// `addFilePost` etc, which will update the hasher. After all files are added, the user can
// use `final`, and will at some point `writeManifest` the file list to disk.
self.hash.hasher = hasher_init;
self.hash.hasher.update(&bin_digest);
hit: {
const file_digests_populated: usize = digests: {
switch (try self.hitWithCurrentLock()) {
.hit => break :hit,
.miss => |m| if (!try self.upgradeToExclusiveLock()) {
break :digests m.file_digests_populated;
},
}
// We've just had a miss with the shared lock, and upgraded to an exclusive lock. Someone
// else might have modified the digest, so we need to check again before deciding to miss.
// Before trying again, we must reset `self.hash.hasher` and `self.files`.
// This is basically just the first half of `unhit`.
self.hash.hasher = hasher_init;
self.hash.hasher.update(&bin_digest);
while (self.files.count() != input_file_count) {
var file = self.files.pop().?;
file.key.deinit(self.cache.gpa);
}
switch (try self.hitWithCurrentLock()) {
.hit => break :hit,
.miss => |m| break :digests m.file_digests_populated,
}
};
// This is a guaranteed cache miss. We're almost ready to return `false`, but there's a
// little bookkeeping to do first. The first `file_digests_populated` entries in `files`
// have their `bin_digest` populated; there may be some left in `input_file_count` which
// we'll need to populate ourselves. Other than that, this is basically `unhit`.
self.manifest_dirty = true;
self.hash.hasher = hasher_init;
self.hash.hasher.update(&bin_digest);
while (self.files.count() != input_file_count) {
var file = self.files.pop().?;
file.key.deinit(self.cache.gpa);
}
for (self.files.keys(), 0..) |*file, idx| {
if (idx < file_digests_populated) {
// `bin_digest` is already populated by `hitWithCurrentLock`, so we can use it directly.
self.hash.hasher.update(&file.bin_digest);
} else {
self.populateFileHash(file) 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;
}