| ... | ... | @@ -40,7 +40,7 @@ pub fn addPrefix(cache: *Cache, directory: Directory) void { |
| 40 | 40 | |
| 41 | 41 | /// Be sure to call `Manifest.deinit` after successful initialization. |
| 42 | 42 | pub fn obtain(cache: *Cache) Manifest { |
| 43 | | return Manifest{ |
| 43 | return .{ |
| 44 | 44 | .cache = cache, |
| 45 | 45 | .hash = cache.hash, |
| 46 | 46 | .manifest_file = null, |
| ... | ... | @@ -99,9 +99,9 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath { |
| 99 | 99 | } |
| 100 | 100 | |
| 101 | 101 | fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 { |
| 102 | | const relative = try std.fs.path.relative(allocator, prefix, path); |
| 102 | const relative = try fs.path.relative(allocator, prefix, path); |
| 103 | 103 | errdefer allocator.free(relative); |
| 104 | | var component_iterator = std.fs.path.NativeComponentIterator.init(relative) catch { |
| 104 | var component_iterator = fs.path.NativeComponentIterator.init(relative) catch { |
| 105 | 105 | return error.NotASubPath; |
| 106 | 106 | }; |
| 107 | 107 | if (component_iterator.root() != null) { |
| ... | ... | @@ -327,13 +327,27 @@ pub const Manifest = struct { |
| 327 | 327 | want_refresh_timestamp: bool = true, |
| 328 | 328 | files: Files = .{}, |
| 329 | 329 | hex_digest: HexDigest, |
| 330 | | /// Populated when hit() returns an error because of one |
| 331 | | /// of the files listed in the manifest. |
| 332 | | failed_file_index: ?usize = null, |
| 330 | diagnostic: Diagnostic = .none, |
| 333 | 331 | /// Keeps track of the last time we performed a file system write to observe |
| 334 | 332 | /// what time the file system thinks it is, according to its own granularity. |
| 335 | 333 | recent_problematic_timestamp: i128 = 0, |
| 336 | 334 | |
| 335 | pub const Diagnostic = union(enum) { |
| 336 | none, |
| 337 | manifest_create: fs.File.OpenError, |
| 338 | manifest_read: fs.File.ReadError, |
| 339 | manifest_lock: fs.File.LockError, |
| 340 | file_open: FileOp, |
| 341 | file_stat: FileOp, |
| 342 | file_read: FileOp, |
| 343 | file_hash: FileOp, |
| 344 | |
| 345 | pub const FileOp = struct { |
| 346 | file_index: usize, |
| 347 | err: anyerror, |
| 348 | }; |
| 349 | }; |
| 350 | |
| 337 | 351 | pub const Files = std.ArrayHashMapUnmanaged(File, void, FilesContext, false); |
| 338 | 352 | |
| 339 | 353 | pub const FilesContext = struct { |
| ... | ... | @@ -452,6 +466,15 @@ pub const Manifest = struct { |
| 452 | 466 | return self.addDepFileMaybePost(dir, dep_file_basename); |
| 453 | 467 | } |
| 454 | 468 | |
| 469 | pub const HitError = error{ |
| 470 | /// Unable to check the cache for a reason that has been recorded into |
| 471 | /// the `diagnostic` field. |
| 472 | CacheCheckFailed, |
| 473 | /// A cache manifest file exists however it could not be parsed. |
| 474 | InvalidFormat, |
| 475 | OutOfMemory, |
| 476 | }; |
| 477 | |
| 455 | 478 | /// Check the cache to see if the input exists in it. If it exists, returns `true`. |
| 456 | 479 | /// A hex encoding of its hash is available by calling `final`. |
| 457 | 480 | /// |
| ... | ... | @@ -464,11 +487,11 @@ pub const Manifest = struct { |
| 464 | 487 | /// The lock on the manifest file is released when `deinit` is called. As another |
| 465 | 488 | /// option, one may call `toOwnedLock` to obtain a smaller object which can represent |
| 466 | 489 | /// the lock. `deinit` is safe to call whether or not `toOwnedLock` has been called. |
| 467 | | pub fn hit(self: *Manifest) !bool { |
| 490 | pub fn hit(self: *Manifest) HitError!bool { |
| 468 | 491 | const gpa = self.cache.gpa; |
| 469 | 492 | assert(self.manifest_file == null); |
| 470 | 493 | |
| 471 | | self.failed_file_index = null; |
| 494 | self.diagnostic = .none; |
| 472 | 495 | |
| 473 | 496 | const ext = ".txt"; |
| 474 | 497 | var manifest_file_path: [hex_digest_len + ext.len]u8 = undefined; |
| ... | ... | @@ -496,17 +519,56 @@ pub const Manifest = struct { |
| 496 | 519 | break; |
| 497 | 520 | } else |err| switch (err) { |
| 498 | 521 | error.WouldBlock => { |
| 499 | | self.manifest_file = try self.cache.manifest_dir.openFile(&manifest_file_path, .{ |
| 522 | self.manifest_file = self.cache.manifest_dir.openFile(&manifest_file_path, .{ |
| 500 | 523 | .mode = .read_write, |
| 501 | 524 | .lock = .shared, |
| 502 | | }); |
| 525 | }) catch |e| { |
| 526 | self.diagnostic = .{ .manifest_create = e }; |
| 527 | return error.CacheCheckFailed; |
| 528 | }; |
| 503 | 529 | break; |
| 504 | 530 | }, |
| 505 | | // There are no dir components, so you would think that this was |
| 506 | | // unreachable, however we have observed on macOS two processes racing |
| 507 | | // to do openat() with O_CREAT manifest in ENOENT. |
| 508 | | error.FileNotFound => continue, |
| 509 | | else => |e| return e, |
| 531 | error.FileNotFound => { |
| 532 | // There are no dir components, so the only possibility |
| 533 | // should be that the directory behind the handle has been |
| 534 | // deleted, however we have observed on macOS two processes |
| 535 | // racing to do openat() with O_CREAT manifest in ENOENT. |
| 536 | // |
| 537 | // As a workaround, we retry with exclusive=true which |
| 538 | // disambiguates by returning EEXIST, indicating original |
| 539 | // failure was a race, or ENOENT, indicating deletion of |
| 540 | // the directory of our open handle. |
| 541 | if (builtin.os.tag != .macos) { |
| 542 | self.diagnostic = .{ .manifest_create = error.FileNotFound }; |
| 543 | return error.CacheCheckFailed; |
| 544 | } |
| 545 | |
| 546 | if (self.cache.manifest_dir.createFile(&manifest_file_path, .{ |
| 547 | .read = true, |
| 548 | .truncate = false, |
| 549 | .lock = .exclusive, |
| 550 | .lock_nonblocking = self.want_shared_lock, |
| 551 | .exclusive = true, |
| 552 | })) |manifest_file| { |
| 553 | self.manifest_file = manifest_file; |
| 554 | self.have_exclusive_lock = true; |
| 555 | break; |
| 556 | } else |excl_err| switch (excl_err) { |
| 557 | error.WouldBlock, error.PathAlreadyExists => continue, |
| 558 | error.FileNotFound => { |
| 559 | self.diagnostic = .{ .manifest_create = error.FileNotFound }; |
| 560 | return error.CacheCheckFailed; |
| 561 | }, |
| 562 | else => |e| { |
| 563 | self.diagnostic = .{ .manifest_create = e }; |
| 564 | return error.CacheCheckFailed; |
| 565 | }, |
| 566 | } |
| 567 | }, |
| 568 | else => |e| { |
| 569 | self.diagnostic = .{ .manifest_create = e }; |
| 570 | return error.CacheCheckFailed; |
| 571 | }, |
| 510 | 572 | } |
| 511 | 573 | } |
| 512 | 574 | |
| ... | ... | @@ -514,7 +576,14 @@ pub const Manifest = struct { |
| 514 | 576 | |
| 515 | 577 | const input_file_count = self.files.entries.len; |
| 516 | 578 | while (true) : (self.unhit(bin_digest, input_file_count)) { |
| 517 | | const file_contents = try self.manifest_file.?.reader().readAllAlloc(gpa, manifest_file_size_max); |
| 579 | const file_contents = self.manifest_file.?.reader().readAllAlloc(gpa, manifest_file_size_max) catch |err| switch (err) { |
| 580 | error.OutOfMemory => return error.OutOfMemory, |
| 581 | error.StreamTooLong => return error.OutOfMemory, |
| 582 | else => |e| { |
| 583 | self.diagnostic = .{ .manifest_read = e }; |
| 584 | return error.CacheCheckFailed; |
| 585 | }, |
| 586 | }; |
| 518 | 587 | defer gpa.free(file_contents); |
| 519 | 588 | |
| 520 | 589 | var any_file_changed = false; |
| ... | ... | @@ -526,8 +595,11 @@ pub const Manifest = struct { |
| 526 | 595 | while (idx < input_file_count) : (idx += 1) { |
| 527 | 596 | const ch_file = &self.files.keys()[idx]; |
| 528 | 597 | self.populateFileHash(ch_file) catch |err| { |
| 529 | | self.failed_file_index = idx; |
| 530 | | return err; |
| 598 | self.diagnostic = .{ .file_hash = .{ |
| 599 | .file_index = idx, |
| 600 | .err = err, |
| 601 | } }; |
| 602 | return error.CacheCheckFailed; |
| 531 | 603 | }; |
| 532 | 604 | } |
| 533 | 605 | return false; |
| ... | ... | @@ -605,13 +677,22 @@ pub const Manifest = struct { |
| 605 | 677 | if (try self.upgradeToExclusiveLock()) continue; |
| 606 | 678 | return false; |
| 607 | 679 | }, |
| 608 | | else => return error.CacheUnavailable, |
| 680 | else => |e| { |
| 681 | self.diagnostic = .{ .file_open = .{ |
| 682 | .file_index = idx, |
| 683 | .err = e, |
| 684 | } }; |
| 685 | return error.CacheCheckFailed; |
| 686 | }, |
| 609 | 687 | }; |
| 610 | 688 | defer this_file.close(); |
| 611 | 689 | |
| 612 | 690 | const actual_stat = this_file.stat() catch |err| { |
| 613 | | self.failed_file_index = idx; |
| 614 | | return err; |
| 691 | self.diagnostic = .{ .file_stat = .{ |
| 692 | .file_index = idx, |
| 693 | .err = err, |
| 694 | } }; |
| 695 | return error.CacheCheckFailed; |
| 615 | 696 | }; |
| 616 | 697 | const size_match = actual_stat.size == cache_hash_file.stat.size; |
| 617 | 698 | const mtime_match = actual_stat.mtime == cache_hash_file.stat.mtime; |
| ... | ... | @@ -634,8 +715,11 @@ pub const Manifest = struct { |
| 634 | 715 | |
| 635 | 716 | var actual_digest: BinDigest = undefined; |
| 636 | 717 | hashFile(this_file, &actual_digest) catch |err| { |
| 637 | | self.failed_file_index = idx; |
| 638 | | return err; |
| 718 | self.diagnostic = .{ .file_read = .{ |
| 719 | .file_index = idx, |
| 720 | .err = err, |
| 721 | } }; |
| 722 | return error.CacheCheckFailed; |
| 639 | 723 | }; |
| 640 | 724 | |
| 641 | 725 | if (!mem.eql(u8, &cache_hash_file.bin_digest, &actual_digest)) { |
| ... | ... | @@ -662,17 +746,22 @@ pub const Manifest = struct { |
| 662 | 746 | if (try self.upgradeToExclusiveLock()) continue; |
| 663 | 747 | self.manifest_dirty = true; |
| 664 | 748 | while (idx < input_file_count) : (idx += 1) { |
| 665 | | const ch_file = &self.files.keys()[idx]; |
| 666 | | self.populateFileHash(ch_file) catch |err| { |
| 667 | | self.failed_file_index = idx; |
| 668 | | return err; |
| 749 | self.populateFileHash(&self.files.keys()[idx]) catch |err| { |
| 750 | self.diagnostic = .{ .file_hash = .{ |
| 751 | .file_index = idx, |
| 752 | .err = err, |
| 753 | } }; |
| 754 | return error.CacheCheckFailed; |
| 669 | 755 | }; |
| 670 | 756 | } |
| 671 | 757 | return false; |
| 672 | 758 | } |
| 673 | 759 | |
| 674 | 760 | if (self.want_shared_lock) { |
| 675 | | try self.downgradeToSharedLock(); |
| 761 | self.downgradeToSharedLock() catch |err| { |
| 762 | self.diagnostic = .{ .manifest_lock = err }; |
| 763 | return error.CacheCheckFailed; |
| 764 | }; |
| 676 | 765 | } |
| 677 | 766 | |
| 678 | 767 | return true; |
| ... | ... | @@ -1010,7 +1099,7 @@ pub const Manifest = struct { |
| 1010 | 1099 | self.have_exclusive_lock = false; |
| 1011 | 1100 | } |
| 1012 | 1101 | |
| 1013 | | fn upgradeToExclusiveLock(self: *Manifest) !bool { |
| 1102 | fn upgradeToExclusiveLock(self: *Manifest) error{CacheCheckFailed}!bool { |
| 1014 | 1103 | if (self.have_exclusive_lock) return false; |
| 1015 | 1104 | assert(self.manifest_file != null); |
| 1016 | 1105 | |
| ... | ... | @@ -1022,7 +1111,10 @@ pub const Manifest = struct { |
| 1022 | 1111 | // Here we intentionally have a period where the lock is released, in case there are |
| 1023 | 1112 | // other processes holding a shared lock. |
| 1024 | 1113 | manifest_file.unlock(); |
| 1025 | | try manifest_file.lock(.exclusive); |
| 1114 | manifest_file.lock(.exclusive) catch |err| { |
| 1115 | self.diagnostic = .{ .manifest_lock = err }; |
| 1116 | return error.CacheCheckFailed; |
| 1117 | }; |
| 1026 | 1118 | } |
| 1027 | 1119 | self.have_exclusive_lock = true; |
| 1028 | 1120 | return true; |
| ... | ... | @@ -1132,7 +1224,7 @@ pub fn writeSmallFile(dir: fs.Dir, sub_path: []const u8, data: []const u8) !void |
| 1132 | 1224 | } |
| 1133 | 1225 | } |
| 1134 | 1226 | |
| 1135 | | fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) !void { |
| 1227 | fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadError!void { |
| 1136 | 1228 | var buf: [1024]u8 = undefined; |
| 1137 | 1229 | var hasher = hasher_init; |
| 1138 | 1230 | var off: u64 = 0; |