| ... | ... | @@ -1,6 +1,7 @@ |
| 1 | 1 | gpa: Allocator, |
| 2 | 2 | manifest_dir: fs.Dir, |
| 3 | 3 | hash: HashHelper = .{}, |
| 4 | recent_problematic_timestamp: i128 = 0, |
| 4 | 5 | |
| 5 | 6 | const Cache = @This(); |
| 6 | 7 | const std = @import("std"); |
| ... | ... | @@ -16,7 +17,7 @@ const Compilation = @import("Compilation.zig"); |
| 16 | 17 | const log = std.log.scoped(.cache); |
| 17 | 18 | |
| 18 | 19 | /// Be sure to call `Manifest.deinit` after successful initialization. |
| 19 | | pub fn obtain(cache: *const Cache) Manifest { |
| 20 | pub fn obtain(cache: *Cache) Manifest { |
| 20 | 21 | return Manifest{ |
| 21 | 22 | .cache = cache, |
| 22 | 23 | .hash = cache.hash, |
| ... | ... | @@ -170,7 +171,7 @@ pub const Lock = struct { |
| 170 | 171 | /// This is not a general-purpose cache. |
| 171 | 172 | /// It is designed to be fast and simple, not to withstand attacks using specially-crafted input. |
| 172 | 173 | pub const Manifest = struct { |
| 173 | | cache: *const Cache, |
| 174 | cache: *Cache, |
| 174 | 175 | /// Current state for incremental hashing. |
| 175 | 176 | hash: HashHelper, |
| 176 | 177 | manifest_file: ?fs.File, |
| ... | ... | @@ -187,9 +188,6 @@ pub const Manifest = struct { |
| 187 | 188 | /// of the files listed in the manifest. |
| 188 | 189 | failed_file_index: ?usize = null, |
| 189 | 190 | |
| 190 | | /// most recent problematic timestamp |
| 191 | | recent_problematic_timestamp: i128 = 0, |
| 192 | | |
| 193 | 191 | /// Add a file as a dependency of process being cached. When `hit` is |
| 194 | 192 | /// called, the file's contents will be checked to ensure that it matches |
| 195 | 193 | /// the contents from previous times. |
| ... | ... | @@ -417,7 +415,7 @@ pub const Manifest = struct { |
| 417 | 415 | |
| 418 | 416 | cache_hash_file.stat = actual_stat; |
| 419 | 417 | |
| 420 | | if (try self.isProblematicTimestamp(cache_hash_file.stat.mtime)) { |
| 418 | if (self.cache.isProblematicTimestamp(cache_hash_file.stat.mtime)) { |
| 421 | 419 | // The actual file has an unreliable timestamp, force it to be hashed |
| 422 | 420 | cache_hash_file.stat.mtime = 0; |
| 423 | 421 | cache_hash_file.stat.inode = 0; |
| ... | ... | @@ -489,7 +487,7 @@ pub const Manifest = struct { |
| 489 | 487 | |
| 490 | 488 | ch_file.stat = try file.stat(); |
| 491 | 489 | |
| 492 | | if (try self.isProblematicTimestamp(ch_file.stat.mtime)) { |
| 490 | if (self.cache.isProblematicTimestamp(ch_file.stat.mtime)) { |
| 493 | 491 | // The actual file has an unreliable timestamp, force it to be hashed |
| 494 | 492 | ch_file.stat.mtime = 0; |
| 495 | 493 | ch_file.stat.inode = 0; |
| ... | ... | @@ -684,26 +682,6 @@ pub const Manifest = struct { |
| 684 | 682 | self.have_exclusive_lock = true; |
| 685 | 683 | } |
| 686 | 684 | |
| 687 | | // Create/Write a file, close it, then grab its stat.mtime timestamp. |
| 688 | | fn isProblematicTimestamp(self: *Manifest, file_time: i128) !bool { |
| 689 | | |
| 690 | | // PERF: Check if the file_time is prior to the most recent problematic timestamp |
| 691 | | // and break out early if so (avoids an I/O to update the recent_problematic_timestamp) |
| 692 | | if (file_time < self.recent_problematic_timestamp) |
| 693 | | return false; |
| 694 | | |
| 695 | | var timestamp_file = try self.cache.manifest_dir.createFile("filetimestamp.tmp", .{ |
| 696 | | .read = true, |
| 697 | | .truncate = false, |
| 698 | | }); |
| 699 | | defer timestamp_file.close(); |
| 700 | | try timestamp_file.setEndPos(0); |
| 701 | | |
| 702 | | self.recent_problematic_timestamp = (try timestamp_file.stat()).mtime; |
| 703 | | |
| 704 | | return (file_time >= self.recent_problematic_timestamp); |
| 705 | | } |
| 706 | | |
| 707 | 685 | /// Obtain only the data needed to maintain a lock on the manifest file. |
| 708 | 686 | /// The `Manifest` remains safe to deinit. |
| 709 | 687 | /// Don't forget to call `writeManifest` before this! |
| ... | ... | @@ -766,16 +744,34 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) !void { |
| 766 | 744 | hasher.final(bin_digest); |
| 767 | 745 | } |
| 768 | 746 | |
| 747 | /// Create/Write a file, grab its stat.mtime timestamp, then close it. |
| 748 | /// If any filesystem errors occur, this function returns `true`. |
| 749 | fn isProblematicTimestamp(cache: *Cache, file_time: i128) bool { |
| 750 | // If the file_time is prior to the most recent problematic timestamp |
| 751 | // then we don't need to access the filesystem. |
| 752 | if (file_time < cache.recent_problematic_timestamp) |
| 753 | return false; |
| 754 | |
| 755 | var file = cache.manifest_dir.createFile("timestamp", .{ |
| 756 | .read = true, |
| 757 | .truncate = true, |
| 758 | }) catch return true; |
| 759 | defer file.close(); |
| 760 | |
| 761 | cache.recent_problematic_timestamp = (file.stat() catch return true).mtime; |
| 762 | |
| 763 | return file_time >= cache.recent_problematic_timestamp; |
| 764 | } |
| 765 | |
| 769 | 766 | // Create/Write a file, close it, then grab its stat.mtime timestamp. |
| 770 | 767 | fn testGetCurrentFileTimestamp() !i128 { |
| 771 | | var timestamp_file = try fs.cwd().createFile("zig-cache/filetimestamp.tmp", .{ |
| 768 | var file = try fs.cwd().createFile("test-filetimestamp.tmp", .{ |
| 772 | 769 | .read = true, |
| 773 | | .truncate = false, |
| 770 | .truncate = true, |
| 774 | 771 | }); |
| 775 | | defer timestamp_file.close(); |
| 776 | | try timestamp_file.setEndPos(0); |
| 772 | defer file.close(); |
| 777 | 773 | |
| 778 | | return (try timestamp_file.stat()).mtime; |
| 774 | return (try file.stat()).mtime; |
| 779 | 775 | } |
| 780 | 776 | |
| 781 | 777 | test "cache file and then recall it" { |