| ... | @@ -337,6 +337,7 @@ pub const Manifest = struct { | ... | @@ -337,6 +337,7 @@ pub const Manifest = struct { |
| 337 | manifest_create: fs.File.OpenError, | 337 | manifest_create: fs.File.OpenError, |
| 338 | manifest_read: fs.File.ReadError, | 338 | manifest_read: fs.File.ReadError, |
| 339 | manifest_lock: fs.File.LockError, | 339 | manifest_lock: fs.File.LockError, |
| | 340 | manifest_seek: fs.File.SeekError, |
| 340 | file_open: FileOp, | 341 | file_open: FileOp, |
| 341 | file_stat: FileOp, | 342 | file_stat: FileOp, |
| 342 | file_read: FileOp, | 343 | file_read: FileOp, |
| ... | @@ -488,7 +489,6 @@ pub const Manifest = struct { | ... | @@ -488,7 +489,6 @@ pub const Manifest = struct { |
| 488 | /// option, one may call `toOwnedLock` to obtain a smaller object which can represent | 489 | /// option, one may call `toOwnedLock` to obtain a smaller object which can represent |
| 489 | /// the lock. `deinit` is safe to call whether or not `toOwnedLock` has been called. | 490 | /// the lock. `deinit` is safe to call whether or not `toOwnedLock` has been called. |
| 490 | pub fn hit(self: *Manifest) HitError!bool { | 491 | pub fn hit(self: *Manifest) HitError!bool { |
| 491 | const gpa = self.cache.gpa; | | |
| 492 | assert(self.manifest_file == null); | 492 | assert(self.manifest_file == null); |
| 493 | | 493 | |
| 494 | self.diagnostic = .none; | 494 | self.diagnostic = .none; |
| ... | @@ -501,12 +501,12 @@ pub const Manifest = struct { | ... | @@ -501,12 +501,12 @@ pub const Manifest = struct { |
| 501 | | 501 | |
| 502 | self.hex_digest = binToHex(bin_digest); | 502 | self.hex_digest = binToHex(bin_digest); |
| 503 | | 503 | |
| 504 | self.hash.hasher = hasher_init; | | |
| 505 | self.hash.hasher.update(&bin_digest); | | |
| 506 | | | |
| 507 | @memcpy(manifest_file_path[0..self.hex_digest.len], &self.hex_digest); | 504 | @memcpy(manifest_file_path[0..self.hex_digest.len], &self.hex_digest); |
| 508 | manifest_file_path[hex_digest_len..][0..ext.len].* = ext.*; | 505 | manifest_file_path[hex_digest_len..][0..ext.len].* = ext.*; |
| 509 | | 506 | |
| | 507 | // We'll try to open the cache with an exclusive lock, but if that would block |
| | 508 | // and `want_shared_lock` is set, a shared lock might be sufficient, so we'll |
| | 509 | // open with a shared lock instead. |
| 510 | while (true) { | 510 | while (true) { |
| 511 | if (self.cache.manifest_dir.createFile(&manifest_file_path, .{ | 511 | if (self.cache.manifest_dir.createFile(&manifest_file_path, .{ |
| 512 | .read = true, | 512 | .read = true, |
| ... | @@ -575,26 +575,71 @@ pub const Manifest = struct { | ... | @@ -575,26 +575,71 @@ pub const Manifest = struct { |
| 575 | self.want_refresh_timestamp = true; | 575 | self.want_refresh_timestamp = true; |
| 576 | | 576 | |
| 577 | const input_file_count = self.files.entries.len; | 577 | const input_file_count = self.files.entries.len; |
| 578 | while (true) : (self.unhit(bin_digest, input_file_count)) { | 578 | |
| 579 | const file_contents = self.manifest_file.?.reader().readAllAlloc(gpa, manifest_file_size_max) catch |err| switch (err) { | 579 | // We're going to construct a second hash. Its input will begin with the digest we've |
| 580 | error.OutOfMemory => return error.OutOfMemory, | 580 | // already computed (`bin_digest`), and then it'll have the digests of each input file, |
| 581 | error.StreamTooLong => return error.OutOfMemory, | 581 | // including "post" files (see `addFilePost`). If this is a hit, we learn the set of "post" |
| 582 | else => |e| { | 582 | // files from the manifest on disk. If this is a miss, we'll learn those from future calls |
| 583 | self.diagnostic = .{ .manifest_read = e }; | 583 | // to `addFilePost` etc. As such, the state of `self.hash.hasher` after this function |
| | 584 | // depends on whether this is a hit or a miss. |
| | 585 | // |
| | 586 | // If we return `true` indicating a cache hit, then `self.hash.hasher` must already include |
| | 587 | // the digests of the "post" files, so the caller can call `final`. Otherwise, on a cache |
| | 588 | // miss, `self.hash.hasher` will include the digests of all non-"post" files -- that is, |
| | 589 | // the ones we've already been told about. The rest will be discovered through calls to |
| | 590 | // `addFilePost` etc, which will update the hasher. After all files are added, the user can |
| | 591 | // use `final`, and will at some point `writeManifest` the file list to disk. |
| | 592 | |
| | 593 | self.hash.hasher = hasher_init; |
| | 594 | self.hash.hasher.update(&bin_digest); |
| | 595 | |
| | 596 | hit: { |
| | 597 | const file_digests_populated: usize = digests: { |
| | 598 | switch (try self.hitWithCurrentLock()) { |
| | 599 | .hit => break :hit, |
| | 600 | .miss => |m| if (!try self.upgradeToExclusiveLock()) { |
| | 601 | break :digests m.file_digests_populated; |
| | 602 | }, |
| | 603 | } |
| | 604 | // We've just had a miss with the shared lock, and upgraded to an exclusive lock. Someone |
| | 605 | // else might have modified the digest, so we need to check again before deciding to miss. |
| | 606 | // Before trying again, we must reset `self.hash.hasher` and `self.files`. |
| | 607 | // This is basically just the first half of `unhit`. |
| | 608 | self.hash.hasher = hasher_init; |
| | 609 | self.hash.hasher.update(&bin_digest); |
| | 610 | while (self.files.count() != input_file_count) { |
| | 611 | var file = self.files.pop().?; |
| | 612 | file.key.deinit(self.cache.gpa); |
| | 613 | } |
| | 614 | // Also, seek the file back to the start. |
| | 615 | self.manifest_file.?.seekTo(0) catch |err| { |
| | 616 | self.diagnostic = .{ .manifest_seek = err }; |
| 584 | return error.CacheCheckFailed; | 617 | return error.CacheCheckFailed; |
| 585 | }, | 618 | }; |
| | 619 | |
| | 620 | switch (try self.hitWithCurrentLock()) { |
| | 621 | .hit => break :hit, |
| | 622 | .miss => |m| break :digests m.file_digests_populated, |
| | 623 | } |
| 586 | }; | 624 | }; |
| 587 | defer gpa.free(file_contents); | 625 | |
| 588 | | 626 | // This is a guaranteed cache miss. We're almost ready to return `false`, but there's a |
| 589 | var any_file_changed = false; | 627 | // little bookkeeping to do first. The first `file_digests_populated` entries in `files` |
| 590 | var line_iter = mem.tokenizeScalar(u8, file_contents, '\n'); | 628 | // have their `bin_digest` populated; there may be some left in `input_file_count` which |
| 591 | var idx: usize = 0; | 629 | // we'll need to populate ourselves. Other than that, this is basically `unhit`. |
| 592 | if (if (line_iter.next()) |line| !std.mem.eql(u8, line, manifest_header) else true) { | 630 | self.manifest_dirty = true; |
| 593 | if (try self.upgradeToExclusiveLock()) continue; | 631 | self.hash.hasher = hasher_init; |
| 594 | self.manifest_dirty = true; | 632 | self.hash.hasher.update(&bin_digest); |
| 595 | while (idx < input_file_count) : (idx += 1) { | 633 | while (self.files.count() != input_file_count) { |
| 596 | const ch_file = &self.files.keys()[idx]; | 634 | var file = self.files.pop().?; |
| 597 | self.populateFileHash(ch_file) catch |err| { | 635 | file.key.deinit(self.cache.gpa); |
| | 636 | } |
| | 637 | for (self.files.keys(), 0..) |*file, idx| { |
| | 638 | if (idx < file_digests_populated) { |
| | 639 | // `bin_digest` is already populated by `hitWithCurrentLock`, so we can use it directly. |
| | 640 | self.hash.hasher.update(&file.bin_digest); |
| | 641 | } else { |
| | 642 | self.populateFileHash(file) catch |err| { |
| 598 | self.diagnostic = .{ .file_hash = .{ | 643 | self.diagnostic = .{ .file_hash = .{ |
| 599 | .file_index = idx, | 644 | .file_index = idx, |
| 600 | .err = err, | 645 | .err = err, |
| ... | @@ -602,172 +647,195 @@ pub const Manifest = struct { | ... | @@ -602,172 +647,195 @@ pub const Manifest = struct { |
| 602 | return error.CacheCheckFailed; | 647 | return error.CacheCheckFailed; |
| 603 | }; | 648 | }; |
| 604 | } | 649 | } |
| 605 | return false; | | |
| 606 | } | 650 | } |
| 607 | while (line_iter.next()) |line| { | 651 | return false; |
| 608 | defer idx += 1; | 652 | } |
| 609 | | 653 | |
| 610 | var iter = mem.tokenizeScalar(u8, line, ' '); | 654 | if (self.want_shared_lock) { |
| 611 | const size = iter.next() orelse return error.InvalidFormat; | 655 | self.downgradeToSharedLock() catch |err| { |
| 612 | const inode = iter.next() orelse return error.InvalidFormat; | 656 | self.diagnostic = .{ .manifest_lock = err }; |
| 613 | const mtime_nsec_str = iter.next() orelse return error.InvalidFormat; | 657 | return error.CacheCheckFailed; |
| 614 | const digest_str = iter.next() orelse return error.InvalidFormat; | 658 | }; |
| 615 | const prefix_str = iter.next() orelse return error.InvalidFormat; | 659 | } |
| 616 | const file_path = iter.rest(); | | |
| 617 | | | |
| 618 | const stat_size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat; | | |
| 619 | const stat_inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat; | | |
| 620 | const stat_mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat; | | |
| 621 | const file_bin_digest = b: { | | |
| 622 | if (digest_str.len != hex_digest_len) return error.InvalidFormat; | | |
| 623 | var bd: BinDigest = undefined; | | |
| 624 | _ = fmt.hexToBytes(&bd, digest_str) catch return error.InvalidFormat; | | |
| 625 | break :b bd; | | |
| 626 | }; | | |
| 627 | | 660 | |
| 628 | const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat; | 661 | return true; |
| 629 | if (prefix >= self.cache.prefixes_len) return error.InvalidFormat; | 662 | } |
| 630 | | 663 | |
| 631 | if (file_path.len == 0) return error.InvalidFormat; | 664 | /// Assumes that `self.hash.hasher` has been updated only with the original digest, that |
| | 665 | /// `self.files` contains only the original input files, and that `self.manifest_file.?` is |
| | 666 | /// seeked to the start of the file. |
| | 667 | fn hitWithCurrentLock(self: *Manifest) HitError!union(enum) { |
| | 668 | hit, |
| | 669 | miss: struct { |
| | 670 | file_digests_populated: usize, |
| | 671 | }, |
| | 672 | } { |
| | 673 | const gpa = self.cache.gpa; |
| | 674 | const input_file_count = self.files.entries.len; |
| 632 | | 675 | |
| 633 | const cache_hash_file = f: { | 676 | const file_contents = self.manifest_file.?.reader().readAllAlloc(gpa, manifest_file_size_max) catch |err| switch (err) { |
| 634 | const prefixed_path: PrefixedPath = .{ | 677 | error.OutOfMemory => return error.OutOfMemory, |
| 635 | .prefix = prefix, | 678 | error.StreamTooLong => return error.OutOfMemory, |
| 636 | .sub_path = file_path, // expires with file_contents | 679 | else => |e| { |
| 637 | }; | 680 | self.diagnostic = .{ .manifest_read = e }; |
| 638 | if (idx < input_file_count) { | 681 | return error.CacheCheckFailed; |
| 639 | const file = &self.files.keys()[idx]; | 682 | }, |
| 640 | if (!file.prefixed_path.eql(prefixed_path)) | 683 | }; |
| 641 | return error.InvalidFormat; | 684 | defer gpa.free(file_contents); |
| | 685 | |
| | 686 | var any_file_changed = false; |
| | 687 | var line_iter = mem.tokenizeScalar(u8, file_contents, '\n'); |
| | 688 | var idx: usize = 0; |
| | 689 | const header_valid = valid: { |
| | 690 | const line = line_iter.next() orelse break :valid false; |
| | 691 | break :valid std.mem.eql(u8, line, manifest_header); |
| | 692 | }; |
| | 693 | if (!header_valid) { |
| | 694 | return .{ .miss = .{ .file_digests_populated = 0 } }; |
| | 695 | } |
| | 696 | while (line_iter.next()) |line| { |
| | 697 | defer idx += 1; |
| | 698 | |
| | 699 | var iter = mem.tokenizeScalar(u8, line, ' '); |
| | 700 | const size = iter.next() orelse return error.InvalidFormat; |
| | 701 | const inode = iter.next() orelse return error.InvalidFormat; |
| | 702 | const mtime_nsec_str = iter.next() orelse return error.InvalidFormat; |
| | 703 | const digest_str = iter.next() orelse return error.InvalidFormat; |
| | 704 | const prefix_str = iter.next() orelse return error.InvalidFormat; |
| | 705 | const file_path = iter.rest(); |
| | 706 | |
| | 707 | const stat_size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat; |
| | 708 | const stat_inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat; |
| | 709 | const stat_mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat; |
| | 710 | const file_bin_digest = b: { |
| | 711 | if (digest_str.len != hex_digest_len) return error.InvalidFormat; |
| | 712 | var bd: BinDigest = undefined; |
| | 713 | _ = fmt.hexToBytes(&bd, digest_str) catch return error.InvalidFormat; |
| | 714 | break :b bd; |
| | 715 | }; |
| | 716 | |
| | 717 | const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat; |
| | 718 | if (prefix >= self.cache.prefixes_len) return error.InvalidFormat; |
| | 719 | |
| | 720 | if (file_path.len == 0) return error.InvalidFormat; |
| 642 | | 721 | |
| 643 | file.stat = .{ | 722 | const cache_hash_file = f: { |
| | 723 | const prefixed_path: PrefixedPath = .{ |
| | 724 | .prefix = prefix, |
| | 725 | .sub_path = file_path, // expires with file_contents |
| | 726 | }; |
| | 727 | if (idx < input_file_count) { |
| | 728 | const file = &self.files.keys()[idx]; |
| | 729 | if (!file.prefixed_path.eql(prefixed_path)) |
| | 730 | return error.InvalidFormat; |
| | 731 | |
| | 732 | file.stat = .{ |
| | 733 | .size = stat_size, |
| | 734 | .inode = stat_inode, |
| | 735 | .mtime = stat_mtime, |
| | 736 | }; |
| | 737 | file.bin_digest = file_bin_digest; |
| | 738 | break :f file; |
| | 739 | } |
| | 740 | const gop = try self.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{}); |
| | 741 | errdefer _ = self.files.pop(); |
| | 742 | if (!gop.found_existing) { |
| | 743 | gop.key_ptr.* = .{ |
| | 744 | .prefixed_path = .{ |
| | 745 | .prefix = prefix, |
| | 746 | .sub_path = try gpa.dupe(u8, file_path), |
| | 747 | }, |
| | 748 | .contents = null, |
| | 749 | .max_file_size = null, |
| | 750 | .handle = null, |
| | 751 | .stat = .{ |
| 644 | .size = stat_size, | 752 | .size = stat_size, |
| 645 | .inode = stat_inode, | 753 | .inode = stat_inode, |
| 646 | .mtime = stat_mtime, | 754 | .mtime = stat_mtime, |
| 647 | }; | 755 | }, |
| 648 | file.bin_digest = file_bin_digest; | 756 | .bin_digest = file_bin_digest, |
| 649 | break :f file; | 757 | }; |
| 650 | } | 758 | } |
| 651 | const gop = try self.files.getOrPutAdapted(gpa, prefixed_path, FilesAdapter{}); | 759 | break :f gop.key_ptr; |
| 652 | errdefer _ = self.files.pop(); | 760 | }; |
| 653 | if (!gop.found_existing) { | | |
| 654 | gop.key_ptr.* = .{ | | |
| 655 | .prefixed_path = .{ | | |
| 656 | .prefix = prefix, | | |
| 657 | .sub_path = try gpa.dupe(u8, file_path), | | |
| 658 | }, | | |
| 659 | .contents = null, | | |
| 660 | .max_file_size = null, | | |
| 661 | .handle = null, | | |
| 662 | .stat = .{ | | |
| 663 | .size = stat_size, | | |
| 664 | .inode = stat_inode, | | |
| 665 | .mtime = stat_mtime, | | |
| 666 | }, | | |
| 667 | .bin_digest = file_bin_digest, | | |
| 668 | }; | | |
| 669 | } | | |
| 670 | break :f gop.key_ptr; | | |
| 671 | }; | | |
| 672 | | | |
| 673 | const pp = cache_hash_file.prefixed_path; | | |
| 674 | const dir = self.cache.prefixes()[pp.prefix].handle; | | |
| 675 | const this_file = dir.openFile(pp.sub_path, .{ .mode = .read_only }) catch |err| switch (err) { | | |
| 676 | error.FileNotFound => { | | |
| 677 | if (try self.upgradeToExclusiveLock()) continue; | | |
| 678 | return false; | | |
| 679 | }, | | |
| 680 | else => |e| { | | |
| 681 | self.diagnostic = .{ .file_open = .{ | | |
| 682 | .file_index = idx, | | |
| 683 | .err = e, | | |
| 684 | } }; | | |
| 685 | return error.CacheCheckFailed; | | |
| 686 | }, | | |
| 687 | }; | | |
| 688 | defer this_file.close(); | | |
| 689 | | 761 | |
| 690 | const actual_stat = this_file.stat() catch |err| { | 762 | const pp = cache_hash_file.prefixed_path; |
| 691 | self.diagnostic = .{ .file_stat = .{ | 763 | const dir = self.cache.prefixes()[pp.prefix].handle; |
| | 764 | const this_file = dir.openFile(pp.sub_path, .{ .mode = .read_only }) catch |err| switch (err) { |
| | 765 | error.FileNotFound => { |
| | 766 | // Every digest before this one has been populated successfully. |
| | 767 | return .{ .miss = .{ .file_digests_populated = idx } }; |
| | 768 | }, |
| | 769 | else => |e| { |
| | 770 | self.diagnostic = .{ .file_open = .{ |
| 692 | .file_index = idx, | 771 | .file_index = idx, |
| 693 | .err = err, | 772 | .err = e, |
| 694 | } }; | 773 | } }; |
| 695 | return error.CacheCheckFailed; | 774 | return error.CacheCheckFailed; |
| 696 | }; | 775 | }, |
| 697 | const size_match = actual_stat.size == cache_hash_file.stat.size; | 776 | }; |
| 698 | const mtime_match = actual_stat.mtime == cache_hash_file.stat.mtime; | 777 | defer this_file.close(); |
| 699 | const inode_match = actual_stat.inode == cache_hash_file.stat.inode; | | |
| 700 | | | |
| 701 | if (!size_match or !mtime_match or !inode_match) { | | |
| 702 | self.manifest_dirty = true; | | |
| 703 | | | |
| 704 | cache_hash_file.stat = .{ | | |
| 705 | .size = actual_stat.size, | | |
| 706 | .mtime = actual_stat.mtime, | | |
| 707 | .inode = actual_stat.inode, | | |
| 708 | }; | | |
| 709 | | | |
| 710 | if (self.isProblematicTimestamp(cache_hash_file.stat.mtime)) { | | |
| 711 | // The actual file has an unreliable timestamp, force it to be hashed | | |
| 712 | cache_hash_file.stat.mtime = 0; | | |
| 713 | cache_hash_file.stat.inode = 0; | | |
| 714 | } | | |
| 715 | | | |
| 716 | var actual_digest: BinDigest = undefined; | | |
| 717 | hashFile(this_file, &actual_digest) catch |err| { | | |
| 718 | self.diagnostic = .{ .file_read = .{ | | |
| 719 | .file_index = idx, | | |
| 720 | .err = err, | | |
| 721 | } }; | | |
| 722 | return error.CacheCheckFailed; | | |
| 723 | }; | | |
| 724 | | 778 | |
| 725 | if (!mem.eql(u8, &cache_hash_file.bin_digest, &actual_digest)) { | 779 | const actual_stat = this_file.stat() catch |err| { |
| 726 | cache_hash_file.bin_digest = actual_digest; | 780 | self.diagnostic = .{ .file_stat = .{ |
| 727 | // keep going until we have the input file digests | 781 | .file_index = idx, |
| 728 | any_file_changed = true; | 782 | .err = err, |
| 729 | } | 783 | } }; |
| 730 | } | 784 | return error.CacheCheckFailed; |
| | 785 | }; |
| | 786 | const size_match = actual_stat.size == cache_hash_file.stat.size; |
| | 787 | const mtime_match = actual_stat.mtime == cache_hash_file.stat.mtime; |
| | 788 | const inode_match = actual_stat.inode == cache_hash_file.stat.inode; |
| | 789 | |
| | 790 | if (!size_match or !mtime_match or !inode_match) { |
| | 791 | cache_hash_file.stat = .{ |
| | 792 | .size = actual_stat.size, |
| | 793 | .mtime = actual_stat.mtime, |
| | 794 | .inode = actual_stat.inode, |
| | 795 | }; |
| 731 | | 796 | |
| 732 | if (!any_file_changed) { | 797 | if (self.isProblematicTimestamp(cache_hash_file.stat.mtime)) { |
| 733 | self.hash.hasher.update(&cache_hash_file.bin_digest); | 798 | // The actual file has an unreliable timestamp, force it to be hashed |
| | 799 | cache_hash_file.stat.mtime = 0; |
| | 800 | cache_hash_file.stat.inode = 0; |
| 734 | } | 801 | } |
| 735 | } | | |
| 736 | | 802 | |
| 737 | if (any_file_changed) { | 803 | var actual_digest: BinDigest = undefined; |
| 738 | if (try self.upgradeToExclusiveLock()) continue; | 804 | hashFile(this_file, &actual_digest) catch |err| { |
| 739 | // cache miss | 805 | self.diagnostic = .{ .file_read = .{ |
| 740 | // keep the manifest file open | 806 | .file_index = idx, |
| 741 | self.unhit(bin_digest, input_file_count); | 807 | .err = err, |
| 742 | return false; | 808 | } }; |
| 743 | } | 809 | return error.CacheCheckFailed; |
| | 810 | }; |
| 744 | | 811 | |
| 745 | if (idx < input_file_count) { | 812 | if (!mem.eql(u8, &cache_hash_file.bin_digest, &actual_digest)) { |
| 746 | if (try self.upgradeToExclusiveLock()) continue; | 813 | cache_hash_file.bin_digest = actual_digest; |
| 747 | self.manifest_dirty = true; | 814 | // keep going until we have the input file digests |
| 748 | while (idx < input_file_count) : (idx += 1) { | 815 | any_file_changed = true; |
| 749 | self.populateFileHash(&self.files.keys()[idx]) catch |err| { | | |
| 750 | self.diagnostic = .{ .file_hash = .{ | | |
| 751 | .file_index = idx, | | |
| 752 | .err = err, | | |
| 753 | } }; | | |
| 754 | return error.CacheCheckFailed; | | |
| 755 | }; | | |
| 756 | } | 816 | } |
| 757 | return false; | | |
| 758 | } | 817 | } |
| 759 | | 818 | |
| 760 | if (self.want_shared_lock) { | 819 | if (!any_file_changed) { |
| 761 | self.downgradeToSharedLock() catch |err| { | 820 | self.hash.hasher.update(&cache_hash_file.bin_digest); |
| 762 | self.diagnostic = .{ .manifest_lock = err }; | | |
| 763 | return error.CacheCheckFailed; | | |
| 764 | }; | | |
| 765 | } | 821 | } |
| | 822 | } |
| 766 | | 823 | |
| 767 | return true; | 824 | // If the manifest was somehow missing one of our input files, or if any file hash has changed, |
| | 825 | // then this is a cache miss. However, we have successfully populated some or all of the file |
| | 826 | // digests. |
| | 827 | if (any_file_changed or idx < input_file_count) { |
| | 828 | return .{ .miss = .{ .file_digests_populated = idx } }; |
| 768 | } | 829 | } |
| | 830 | |
| | 831 | return .hit; |
| 769 | } | 832 | } |
| 770 | | 833 | |
| | 834 | /// Reset `self.hash.hasher` to the state it should be in after `hit` returns `false`. |
| | 835 | /// The hasher contains the original input digest, and all original input file digests (i.e. |
| | 836 | /// not including post files). |
| | 837 | /// Assumes that `bin_digest` is populated for all files up to `input_file_count`. As such, |
| | 838 | /// this is not necessarily safe to call within `hit`. |
| 771 | pub fn unhit(self: *Manifest, bin_digest: BinDigest, input_file_count: usize) void { | 839 | pub fn unhit(self: *Manifest, bin_digest: BinDigest, input_file_count: usize) void { |
| 772 | // Reset the hash. | 840 | // Reset the hash. |
| 773 | self.hash.hasher = hasher_init; | 841 | self.hash.hasher = hasher_init; |