| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | gpa: Allocator, | 1 | gpa: Allocator, |
| 2 | manifest_dir: fs.Dir, | 2 | manifest_dir: fs.Dir, |
| 3 | hash: HashHelper = .{}, | 3 | hash: HashHelper = .{}, |
| | 4 | recent_problematic_timestamp: i128 = 0, |
| 4 | | 5 | |
| 5 | const Cache = @This(); | 6 | const Cache = @This(); |
| 6 | const std = @import("std"); | 7 | const std = @import("std"); |
| ... | @@ -16,7 +17,7 @@ const Compilation = @import("Compilation.zig"); | ... | @@ -16,7 +17,7 @@ const Compilation = @import("Compilation.zig"); |
| 16 | const log = std.log.scoped(.cache); | 17 | const log = std.log.scoped(.cache); |
| 17 | | 18 | |
| 18 | /// Be sure to call `Manifest.deinit` after successful initialization. | 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 | return Manifest{ | 21 | return Manifest{ |
| 21 | .cache = cache, | 22 | .cache = cache, |
| 22 | .hash = cache.hash, | 23 | .hash = cache.hash, |
| ... | @@ -170,7 +171,7 @@ pub const Lock = struct { | ... | @@ -170,7 +171,7 @@ pub const Lock = struct { |
| 170 | /// This is not a general-purpose cache. | 171 | /// This is not a general-purpose cache. |
| 171 | /// It is designed to be fast and simple, not to withstand attacks using specially-crafted input. | 172 | /// It is designed to be fast and simple, not to withstand attacks using specially-crafted input. |
| 172 | pub const Manifest = struct { | 173 | pub const Manifest = struct { |
| 173 | cache: *const Cache, | 174 | cache: *Cache, |
| 174 | /// Current state for incremental hashing. | 175 | /// Current state for incremental hashing. |
| 175 | hash: HashHelper, | 176 | hash: HashHelper, |
| 176 | manifest_file: ?fs.File, | 177 | manifest_file: ?fs.File, |
| ... | @@ -187,9 +188,6 @@ pub const Manifest = struct { | ... | @@ -187,9 +188,6 @@ pub const Manifest = struct { |
| 187 | /// of the files listed in the manifest. | 188 | /// of the files listed in the manifest. |
| 188 | failed_file_index: ?usize = null, | 189 | failed_file_index: ?usize = null, |
| 189 | | 190 | |
| 190 | /// most recent problematic timestamp | | |
| 191 | recent_problematic_timestamp: i128 = 0, | | |
| 192 | | | |
| 193 | /// Add a file as a dependency of process being cached. When `hit` is | 191 | /// Add a file as a dependency of process being cached. When `hit` is |
| 194 | /// called, the file's contents will be checked to ensure that it matches | 192 | /// called, the file's contents will be checked to ensure that it matches |
| 195 | /// the contents from previous times. | 193 | /// the contents from previous times. |
| ... | @@ -417,7 +415,7 @@ pub const Manifest = struct { | ... | @@ -417,7 +415,7 @@ pub const Manifest = struct { |
| 417 | | 415 | |
| 418 | cache_hash_file.stat = actual_stat; | 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 | // The actual file has an unreliable timestamp, force it to be hashed | 419 | // The actual file has an unreliable timestamp, force it to be hashed |
| 422 | cache_hash_file.stat.mtime = 0; | 420 | cache_hash_file.stat.mtime = 0; |
| 423 | cache_hash_file.stat.inode = 0; | 421 | cache_hash_file.stat.inode = 0; |
| ... | @@ -489,7 +487,7 @@ pub const Manifest = struct { | ... | @@ -489,7 +487,7 @@ pub const Manifest = struct { |
| 489 | | 487 | |
| 490 | ch_file.stat = try file.stat(); | 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 | // The actual file has an unreliable timestamp, force it to be hashed | 491 | // The actual file has an unreliable timestamp, force it to be hashed |
| 494 | ch_file.stat.mtime = 0; | 492 | ch_file.stat.mtime = 0; |
| 495 | ch_file.stat.inode = 0; | 493 | ch_file.stat.inode = 0; |
| ... | @@ -684,26 +682,6 @@ pub const Manifest = struct { | ... | @@ -684,26 +682,6 @@ pub const Manifest = struct { |
| 684 | self.have_exclusive_lock = true; | 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 | /// Obtain only the data needed to maintain a lock on the manifest file. | 685 | /// Obtain only the data needed to maintain a lock on the manifest file. |
| 708 | /// The `Manifest` remains safe to deinit. | 686 | /// The `Manifest` remains safe to deinit. |
| 709 | /// Don't forget to call `writeManifest` before this! | 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,16 +744,34 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) !void { |
| 766 | hasher.final(bin_digest); | 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 | // Create/Write a file, close it, then grab its stat.mtime timestamp. | 766 | // Create/Write a file, close it, then grab its stat.mtime timestamp. |
| 770 | fn testGetCurrentFileTimestamp() !i128 { | 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 | .read = true, | 769 | .read = true, |
| 773 | .truncate = false, | 770 | .truncate = true, |
| 774 | }); | 771 | }); |
| 775 | defer timestamp_file.close(); | 772 | defer file.close(); |
| 776 | try timestamp_file.setEndPos(0); | | |
| 777 | | 773 | |
| 778 | return (try timestamp_file.stat()).mtime; | 774 | return (try file.stat()).mtime; |
| 779 | } | 775 | } |
| 780 | | 776 | |
| 781 | test "cache file and then recall it" { | 777 | test "cache file and then recall it" { |