authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-09 18:55:20-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-09 22:10:31-07:00
log4da83feccb4b0e59afdcce9796b08cc4fc8346ae
tree789f655f262a31da1495e7ce04735a45eed078ab
parent72ee042ab0c6dc823f839bca6d1511a144f62b49

Cache: improvements to previous commit

* put `recent_problematic_timestamp` onto `Cache` so that it can be shared by multiple Manifest instances. * make `isProblematicTimestamp` return true on any filesystem error. * save 1 syscall by using truncate=true in createFile instead of calling `setEndPos`.

1 files changed, 28 insertions(+), 32 deletions(-)

src/Cache.zig+28-32
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1gpa: Allocator,1gpa: Allocator,
2manifest_dir: fs.Dir,2manifest_dir: fs.Dir,
3hash: HashHelper = .{},3hash: HashHelper = .{},
4recent_problematic_timestamp: i128 = 0,
45
5const Cache = @This();6const Cache = @This();
6const std = @import("std");7const std = @import("std");
...@@ -16,7 +17,7 @@ const Compilation = @import("Compilation.zig");...@@ -16,7 +17,7 @@ const Compilation = @import("Compilation.zig");
16const log = std.log.scoped(.cache);17const log = std.log.scoped(.cache);
1718
18/// Be sure to call `Manifest.deinit` after successful initialization.19/// Be sure to call `Manifest.deinit` after successful initialization.
19pub fn obtain(cache: *const Cache) Manifest {20pub 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.
172pub const Manifest = struct {173pub 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,
189190
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` is191 /// 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 matches192 /// 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 {
417415
418 cache_hash_file.stat = actual_stat;416 cache_hash_file.stat = actual_stat;
419417
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 hashed419 // 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 {
489487
490 ch_file.stat = try file.stat();488 ch_file.stat = try file.stat();
491489
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 hashed491 // 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 }
686684
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}
768746
747/// Create/Write a file, grab its stat.mtime timestamp, then close it.
748/// If any filesystem errors occur, this function returns `true`.
749fn 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.
770fn testGetCurrentFileTimestamp() !i128 {767fn 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);
777773
778 return (try timestamp_file.stat()).mtime;774 return (try file.stat()).mtime;
779}775}
780776
781test "cache file and then recall it" {777test "cache file and then recall it" {