| ... | ... | @@ -873,9 +873,6 @@ pub const Manifest = struct { |
| 873 | 873 | // through calls to `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user |
| 874 | 874 | // can use `final`, and will at some point `finalize` the file list to disk. |
| 875 | 875 | |
| 876 | | man.hash.hasher = hasher_init; |
| 877 | | man.hash.hasher.update(&input_digest); |
| 878 | | |
| 879 | 876 | hit: { |
| 880 | 877 | const miss_result = miss: { |
| 881 | 878 | const result = try man.checkLocked(); |
| ... | ... | @@ -884,12 +881,8 @@ pub const Manifest = struct { |
| 884 | 881 | } else if (!try man.upgradeToExclusiveLock()) { |
| 885 | 882 | break :miss result; |
| 886 | 883 | } |
| 887 | | // We've just had a miss with the shared lock, and upgraded to an exclusive lock. Someone |
| 888 | | // else might have modified the digest, so we need to check again before deciding to miss. |
| 889 | | // Before trying again, we must reset `man.hash.hasher` and `man.files`. |
| 890 | | // This is basically just the first half of `undoCheck`. |
| 891 | | man.hash.hasher = hasher_init; |
| 892 | | man.hash.hasher.update(&input_digest); |
| 884 | // Missed with the shared lock, and upgraded to an exclusive lock. However, another process may have |
| 885 | // modified the cache directory, so we need to check again before deciding to miss. |
| 893 | 886 | man.shrinkFilesToInput(); |
| 894 | 887 | const refreshed_result = try man.checkLocked(); |
| 895 | 888 | if (refreshed_result == .hit) break :hit; |
| ... | ... | @@ -899,7 +892,8 @@ pub const Manifest = struct { |
| 899 | 892 | // Cache miss, but `checkLocked` guarantees that all input files have their digests computed, even on a |
| 900 | 893 | // cache miss, which is needed because they will be used in the manifest digest. |
| 901 | 894 | man.dirty = true; |
| 902 | | undoCheck(man, &input_digest); |
| 895 | man.shrinkFilesToInput(); |
| 896 | hashFiles(man, &input_digest); |
| 903 | 897 | return miss_result; |
| 904 | 898 | } |
| 905 | 899 | |
| ... | ... | @@ -910,9 +904,23 @@ pub const Manifest = struct { |
| 910 | 904 | }; |
| 911 | 905 | } |
| 912 | 906 | |
| 907 | hashFiles(man, &input_digest); |
| 913 | 908 | return .hit; |
| 914 | 909 | } |
| 915 | 910 | |
| 911 | fn hashFiles(m: *Manifest, input_digest: *BinDigest) void { |
| 912 | const contents = m.contents.items; |
| 913 | |
| 914 | m.hash.hasher = hasher_init; |
| 915 | m.hash.hasher.update(input_digest); |
| 916 | |
| 917 | for (m.files.keys(), 0..) |off, i| { |
| 918 | const file = off.get(contents); |
| 919 | log.warn("hashing {d} {s} = {x}", .{ i, off.path(contents), &file.digest }); |
| 920 | m.hash.hasher.update(&file.digest); |
| 921 | } |
| 922 | } |
| 923 | |
| 916 | 924 | fn shrinkFilesToInput(m: *Manifest) void { |
| 917 | 925 | if (m.files.count() <= m.input_paths.items.len) return; |
| 918 | 926 | // Reads from files hash map whose data is destroyed on the next line. |
| ... | ... | @@ -923,9 +931,9 @@ pub const Manifest = struct { |
| 923 | 931 | assert(mem.isAligned(m.contents.items.len, @alignOf(File))); |
| 924 | 932 | } |
| 925 | 933 | |
| 926 | | /// Assumes that `self.hash.hasher` has been updated only with the original digest and that |
| 927 | | /// `self.files` contains only the original input files. |
| 934 | /// Does not observe or modify `self.hash.hasher`. Asserts `self.files` contains only the original input files. |
| 928 | 935 | fn checkLocked(m: *Manifest) CheckError!CheckResult { |
| 936 | assert(m.files.count() == m.input_paths.items.len); |
| 929 | 937 | const gpa = m.cache.gpa; |
| 930 | 938 | const io = m.cache.io; |
| 931 | 939 | const manifest_file = m.manifest_file.?; |
| ... | ... | @@ -938,7 +946,7 @@ pub const Manifest = struct { |
| 938 | 946 | if (manifest_size == 0 or manifest_size < m.contents.items.len) { |
| 939 | 947 | // Manifest file was never finalized. |
| 940 | 948 | try m.contents.ensureUnusedCapacity(gpa, 1); |
| 941 | | return missInput(m, 0, m.contents.items.len, manifest_size, .incomplete_manifest); |
| 949 | return populateMissingInputFileHashes(m, 0, .incomplete_manifest); |
| 942 | 950 | } |
| 943 | 951 | |
| 944 | 952 | // We must not clobber existing `Manifest.contents` because it possibly contains prepopulated stat and digest |
| ... | ... | @@ -957,7 +965,7 @@ pub const Manifest = struct { |
| 957 | 965 | error.Canceled => |e| return e, |
| 958 | 966 | else => |e| return fail(&m.diagnostic, .{ .manifest_read = e }), |
| 959 | 967 | }; |
| 960 | | if (n != manifest_size) return checkLockedMiss(m, input_contents_len, .incomplete_manifest); |
| 968 | if (n != manifest_size) return missInput(m, 0, input_contents_len, manifest_size, .incomplete_manifest); |
| 961 | 969 | } |
| 962 | 970 | const disk_contents = m.contents.items[0..manifest_size]; |
| 963 | 971 | const input_contents = m.contents.items[manifest_size..][0..input_contents_len]; |
| ... | ... | @@ -1004,8 +1012,6 @@ pub const Manifest = struct { |
| 1004 | 1012 | const file_valid = off + 1 == disk_contents.len and disk_contents[off] == 0; |
| 1005 | 1013 | if (!file_valid) return checkLockedMiss(m, input_contents_len, .incomplete_manifest); |
| 1006 | 1014 | |
| 1007 | | for (m.files.keys()) |file_off| m.hash.hasher.update(&file_off.get(disk_contents).digest); |
| 1008 | | |
| 1009 | 1015 | // Since it's a cache hit, we accept the input file contents from disk and discard the other copy. |
| 1010 | 1016 | // Furthermore, don't track the trailing zero byte in contents. |
| 1011 | 1017 | m.contents.shrinkRetainingCapacity(manifest_size - 1); |
| ... | ... | @@ -1035,7 +1041,15 @@ pub const Manifest = struct { |
| 1035 | 1041 | m.contents.items[manifest_size + off ..][0..copy_len], |
| 1036 | 1042 | ); |
| 1037 | 1043 | m.contents.shrinkRetainingCapacity(input_contents_len); |
| 1038 | | // And now iterate over remaining input files and populate the missing input file hashes. |
| 1044 | return populateMissingInputFileHashes(m, next_file_index, result); |
| 1045 | } |
| 1046 | |
| 1047 | fn populateMissingInputFileHashes( |
| 1048 | m: *Manifest, |
| 1049 | next_file_index: usize, |
| 1050 | result: CheckResult, |
| 1051 | ) CheckError!CheckResult { |
| 1052 | const file_offs = m.files.keys(); |
| 1039 | 1053 | const contents = m.contents.items; |
| 1040 | 1054 | for (file_offs[next_file_index..], m.input_paths.items[next_file_index..]) |input_file_off, *input_path| { |
| 1041 | 1055 | try populateInputPath(m, input_file_off, input_path, contents); |
| ... | ... | @@ -1515,23 +1529,6 @@ pub const Manifest = struct { |
| 1515 | 1529 | return error.CacheCheckFailed; |
| 1516 | 1530 | } |
| 1517 | 1531 | |
| 1518 | | /// Reset `man.hash.hasher` to the state it should be in after `check` returns `CheckResult.miss`. |
| 1519 | | /// The hasher contains the original input digest, and all original input file digests (i.e. |
| 1520 | | /// not including discovered files). |
| 1521 | | /// |
| 1522 | | /// Assumes that `digest` is populated for all input files. |
| 1523 | | pub fn undoCheck(man: *Manifest, input_digest: *const BinDigest) void { |
| 1524 | | // Reset the hash. |
| 1525 | | man.hash.hasher = hasher_init; |
| 1526 | | man.hash.hasher.update(input_digest); |
| 1527 | | man.shrinkFilesToInput(); |
| 1528 | | const contents = man.contents.items; |
| 1529 | | for (man.files.keys()) |off| { |
| 1530 | | const file = off.get(contents); |
| 1531 | | man.hash.hasher.update(&file.digest); |
| 1532 | | } |
| 1533 | | } |
| 1534 | | |
| 1535 | 1532 | fn isProblematicTimestamp(man: *Manifest, timestamp: Io.Timestamp) error{Canceled}!bool { |
| 1536 | 1533 | const io = man.cache.io; |
| 1537 | 1534 | |
| ... | ... | @@ -1844,22 +1841,20 @@ pub const Manifest = struct { |
| 1844 | 1841 | }; |
| 1845 | 1842 | } |
| 1846 | 1843 | |
| 1847 | | /// Returns a binary hash of the inputs. |
| 1844 | /// Returns a binary hash of the inputs, input file contents, and discovered file contents. |
| 1848 | 1845 | pub fn finalBin(self: *Manifest) BinDigest { |
| 1849 | 1846 | assert(self.manifest_file != null); |
| 1850 | 1847 | |
| 1851 | | // We don't close the manifest file yet, because we want to |
| 1852 | | // keep it locked until the API user is done using it. |
| 1853 | | // We also don't write out the manifest yet, because until |
| 1854 | | // cache_release is called we still might be working on creating |
| 1855 | | // the artifacts to cache. |
| 1848 | // We don't close the manifest file yet, because we want to keep it locked until the API user is done using it. |
| 1849 | // We also don't write out the manifest yet, because until `finalize` is called we still might be working on |
| 1850 | // creating the artifacts to cache. |
| 1856 | 1851 | |
| 1857 | 1852 | var bin_digest: BinDigest = undefined; |
| 1858 | 1853 | self.hash.hasher.final(&bin_digest); |
| 1859 | 1854 | return bin_digest; |
| 1860 | 1855 | } |
| 1861 | 1856 | |
| 1862 | | /// Returns a hex encoded hash of the inputs. |
| 1857 | /// Returns a hex encoded hash of the inputs, input file contents, and discovered file contents. |
| 1863 | 1858 | pub fn final(self: *Manifest) HexDigest { |
| 1864 | 1859 | const bin_digest = self.finalBin(); |
| 1865 | 1860 | return binToHex(bin_digest); |
| ... | ... | @@ -2327,7 +2322,7 @@ test "check that changing a file causes cache miss" { |
| 2327 | 2322 | try man.finalize(); |
| 2328 | 2323 | } |
| 2329 | 2324 | |
| 2330 | | try testing.expect(!mem.eql(u8, digest1[0..], digest2[0..])); |
| 2325 | try testing.expect(!mem.eql(u8, &digest1, &digest2)); |
| 2331 | 2326 | } |
| 2332 | 2327 | } |
| 2333 | 2328 | |