authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-06-27 22:33:17-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-06-29 14:25:04-07:00
log4e61af404e87d76d1428dd46f85f08afe4ad4a93
tree06aa022b477226f0c5f72e5e0a2708896b7318b8
parent6ba6b98b72efcc0468ef7c3fc0cc6499e5d38ea5

stage2: Cache system handles shared objects

Fixes #9139 Fixes #9187

1 files changed, 121 insertions(+), 54 deletions(-)

src/Cache.zig+121-54
......@@ -181,6 +181,12 @@ pub const Manifest = struct {
181181 hash: HashHelper,
182182 manifest_file: ?fs.File,
183183 manifest_dirty: bool,
184 /// Set this flag to true before calling hit() in order to indicate that
185 /// upon a cache hit, the code using the cache will not modify the files
186 /// within the cache directory. This allows multiple processes to utilize
187 /// the same cache directory at the same time.
188 want_shared_lock: bool = true,
189 have_exclusive_lock: bool = false,
184190 files: std.ArrayListUnmanaged(File) = .{},
185191 hex_digest: [hex_digest_len]u8,
186192 /// Populated when hit() returns an error because of one
......@@ -257,7 +263,9 @@ pub const Manifest = struct {
257263 ///
258264 /// This function will also acquire an exclusive lock to the manifest file. This means
259265 /// that a process holding a Manifest will block any other process attempting to
260 /// acquire the lock.
266 /// acquire the lock. If `want_shared_lock` is `true`, a cache hit guarantees the
267 /// manifest file to be locked in shared mode, and a cache miss guarantees the manifest
268 /// file to be locked in exclusive mode.
261269 ///
262270 /// The lock on the manifest file is released when `deinit` is called. As another
263271 /// option, one may call `toOwnedLock` to obtain a smaller object which can represent
......@@ -285,31 +293,62 @@ pub const Manifest = struct {
285293 mem.copy(u8, &manifest_file_path, &self.hex_digest);
286294 manifest_file_path[self.hex_digest.len..][0..ext.len].* = ext.*;
287295
288 if (self.files.items.len != 0) {
289 self.manifest_file = try self.cache.manifest_dir.createFile(&manifest_file_path, .{
290 .read = true,
291 .truncate = false,
292 .lock = .Exclusive,
293 });
294 } else {
296 if (self.files.items.len == 0) {
295297 // If there are no file inputs, we check if the manifest file exists instead of
296298 // comparing the hashes on the files used for the cached item
297 self.manifest_file = self.cache.manifest_dir.openFile(&manifest_file_path, .{
299 while (true) {
300 if (self.cache.manifest_dir.openFile(&manifest_file_path, .{
301 .read = true,
302 .write = true,
303 .lock = .Exclusive,
304 .lock_nonblocking = self.want_shared_lock,
305 })) |manifest_file| {
306 self.manifest_file = manifest_file;
307 self.have_exclusive_lock = true;
308 break;
309 } else |open_err| switch (open_err) {
310 error.WouldBlock => {
311 self.manifest_file = try self.cache.manifest_dir.openFile(&manifest_file_path, .{
312 .lock = .Shared,
313 });
314 break;
315 },
316 error.FileNotFound => {
317 if (self.cache.manifest_dir.createFile(&manifest_file_path, .{
318 .read = true,
319 .truncate = false,
320 .lock = .Exclusive,
321 .lock_nonblocking = self.want_shared_lock,
322 })) |manifest_file| {
323 self.manifest_file = manifest_file;
324 self.manifest_dirty = true;
325 self.have_exclusive_lock = true;
326 return false; // cache miss; exclusive lock already held
327 } else |err| switch (err) {
328 error.WouldBlock => continue,
329 else => |e| return e,
330 }
331 },
332 else => |e| return e,
333 }
334 }
335 } else {
336 if (self.cache.manifest_dir.createFile(&manifest_file_path, .{
298337 .read = true,
299 .write = true,
338 .truncate = false,
300339 .lock = .Exclusive,
301 }) catch |err| switch (err) {
302 error.FileNotFound => {
303 self.manifest_dirty = true;
304 self.manifest_file = try self.cache.manifest_dir.createFile(&manifest_file_path, .{
305 .read = true,
306 .truncate = false,
307 .lock = .Exclusive,
340 .lock_nonblocking = self.want_shared_lock,
341 })) |manifest_file| {
342 self.manifest_file = manifest_file;
343 self.have_exclusive_lock = true;
344 } else |err| switch (err) {
345 error.WouldBlock => {
346 self.manifest_file = try self.cache.manifest_dir.openFile(&manifest_file_path, .{
347 .lock = .Shared,
308348 });
309 return false;
310349 },
311350 else => |e| return e,
312 };
351 }
313352 }
314353
315354 const file_contents = try self.manifest_file.?.reader().readAllAlloc(self.cache.gpa, manifest_file_size_max);
......@@ -360,7 +399,10 @@ pub const Manifest = struct {
360399 }
361400
362401 const this_file = fs.cwd().openFile(cache_hash_file.path.?, .{ .read = true }) catch |err| switch (err) {
363 error.FileNotFound => return false,
402 error.FileNotFound => {
403 try self.upgradeToExclusiveLock();
404 return false;
405 },
364406 else => return error.CacheUnavailable,
365407 };
366408 defer this_file.close();
......@@ -405,6 +447,7 @@ pub const Manifest = struct {
405447 // cache miss
406448 // keep the manifest file open
407449 self.unhit(bin_digest, input_file_count);
450 try self.upgradeToExclusiveLock();
408451 return false;
409452 }
410453
......@@ -417,9 +460,11 @@ pub const Manifest = struct {
417460 return err;
418461 };
419462 }
463 try self.upgradeToExclusiveLock();
420464 return false;
421465 }
422466
467 try self.downgradeToSharedLock();
423468 return true;
424469 }
425470
......@@ -585,34 +630,56 @@ pub const Manifest = struct {
585630 return out_digest;
586631 }
587632
633 /// If `want_shared_lock` is true, this function automatically downgrades the
634 /// lock from exclusive to shared.
588635 pub fn writeManifest(self: *Manifest) !void {
589636 const manifest_file = self.manifest_file.?;
590 if (!self.manifest_dirty) return;
591
592 var contents = std.ArrayList(u8).init(self.cache.gpa);
593 defer contents.deinit();
637 if (self.manifest_dirty) {
638 self.manifest_dirty = false;
639
640 var contents = std.ArrayList(u8).init(self.cache.gpa);
641 defer contents.deinit();
642
643 const writer = contents.writer();
644 var encoded_digest: [hex_digest_len]u8 = undefined;
645
646 for (self.files.items) |file| {
647 _ = std.fmt.bufPrint(
648 &encoded_digest,
649 "{s}",
650 .{std.fmt.fmtSliceHexLower(&file.bin_digest)},
651 ) catch unreachable;
652 try writer.print("{d} {d} {d} {s} {s}\n", .{
653 file.stat.size,
654 file.stat.inode,
655 file.stat.mtime,
656 &encoded_digest,
657 file.path,
658 });
659 }
594660
595 const writer = contents.writer();
596 var encoded_digest: [hex_digest_len]u8 = undefined;
661 try manifest_file.setEndPos(contents.items.len);
662 try manifest_file.pwriteAll(contents.items, 0);
663 }
597664
598 for (self.files.items) |file| {
599 _ = std.fmt.bufPrint(
600 &encoded_digest,
601 "{s}",
602 .{std.fmt.fmtSliceHexLower(&file.bin_digest)},
603 ) catch unreachable;
604 try writer.print("{d} {d} {d} {s} {s}\n", .{
605 file.stat.size,
606 file.stat.inode,
607 file.stat.mtime,
608 &encoded_digest,
609 file.path,
610 });
665 if (self.want_shared_lock) {
666 try self.downgradeToSharedLock();
611667 }
668 }
669
670 fn downgradeToSharedLock(self: *Manifest) !void {
671 if (!self.have_exclusive_lock) return;
672 const manifest_file = self.manifest_file.?;
673 try manifest_file.setLock(.Shared, false);
674 self.have_exclusive_lock = false;
675 }
612676
613 try manifest_file.setEndPos(contents.items.len);
614 try manifest_file.pwriteAll(contents.items, 0);
615 self.manifest_dirty = false;
677 fn upgradeToExclusiveLock(self: *Manifest) !void {
678 if (self.have_exclusive_lock) return;
679 const manifest_file = self.manifest_file.?;
680 try manifest_file.setLock(.None, false);
681 try manifest_file.setLock(.Exclusive, false);
682 self.have_exclusive_lock = true;
616683 }
617684
618685 /// Obtain only the data needed to maintain a lock on the manifest file.
......@@ -881,27 +948,27 @@ test "no file inputs" {
881948 defer cache.manifest_dir.close();
882949
883950 {
884 var ch = cache.obtain();
885 defer ch.deinit();
951 var man = cache.obtain();
952 defer man.deinit();
886953
887 ch.hash.addBytes("1234");
954 man.hash.addBytes("1234");
888955
889956 // There should be nothing in the cache
890 try testing.expectEqual(false, try ch.hit());
957 try testing.expectEqual(false, try man.hit());
891958
892 digest1 = ch.final();
959 digest1 = man.final();
893960
894 try ch.writeManifest();
961 try man.writeManifest();
895962 }
896963 {
897 var ch = cache.obtain();
898 defer ch.deinit();
964 var man = cache.obtain();
965 defer man.deinit();
899966
900 ch.hash.addBytes("1234");
967 man.hash.addBytes("1234");
901968
902 try testing.expect(try ch.hit());
903 digest2 = ch.final();
904 try ch.writeManifest();
969 try testing.expect(try man.hit());
970 digest2 = man.final();
971 try man.writeManifest();
905972 }
906973
907974 try testing.expectEqual(digest1, digest2);