| ... | @@ -428,149 +428,151 @@ pub const Manifest = struct { | ... | @@ -428,149 +428,151 @@ pub const Manifest = struct { |
| 428 | | 428 | |
| 429 | self.want_refresh_timestamp = true; | 429 | self.want_refresh_timestamp = true; |
| 430 | | 430 | |
| 431 | const file_contents = try self.manifest_file.?.reader().readAllAlloc(gpa, manifest_file_size_max); | 431 | while (true) { |
| 432 | defer gpa.free(file_contents); | 432 | const file_contents = try self.manifest_file.?.reader().readAllAlloc(gpa, manifest_file_size_max); |
| 433 | | 433 | defer gpa.free(file_contents); |
| 434 | const input_file_count = self.files.items.len; | 434 | |
| 435 | var any_file_changed = false; | 435 | const input_file_count = self.files.items.len; |
| 436 | var line_iter = mem.tokenize(u8, file_contents, "\n"); | 436 | var any_file_changed = false; |
| 437 | var idx: usize = 0; | 437 | var line_iter = mem.tokenize(u8, file_contents, "\n"); |
| 438 | if (if (line_iter.next()) |line| !std.mem.eql(u8, line, manifest_header) else true) { | 438 | var idx: usize = 0; |
| 439 | self.manifest_dirty = true; | 439 | if (if (line_iter.next()) |line| !std.mem.eql(u8, line, manifest_header) else true) { |
| 440 | while (idx < input_file_count) : (idx += 1) { | 440 | if (try self.upgradeToExclusiveLock()) continue; |
| 441 | const ch_file = &self.files.items[idx]; | 441 | self.manifest_dirty = true; |
| 442 | self.populateFileHash(ch_file) catch |err| { | 442 | while (idx < input_file_count) : (idx += 1) { |
| 443 | self.failed_file_index = idx; | 443 | const ch_file = &self.files.items[idx]; |
| 444 | return err; | 444 | self.populateFileHash(ch_file) catch |err| { |
| 445 | }; | 445 | self.failed_file_index = idx; |
| | 446 | return err; |
| | 447 | }; |
| | 448 | } |
| | 449 | return false; |
| 446 | } | 450 | } |
| 447 | try self.upgradeToExclusiveLock(); | 451 | while (line_iter.next()) |line| { |
| 448 | return false; | 452 | defer idx += 1; |
| 449 | } | 453 | |
| 450 | while (line_iter.next()) |line| { | 454 | const cache_hash_file = if (idx < input_file_count) &self.files.items[idx] else blk: { |
| 451 | defer idx += 1; | 455 | const new = try self.files.addOne(gpa); |
| 452 | | 456 | new.* = .{ |
| 453 | const cache_hash_file = if (idx < input_file_count) &self.files.items[idx] else blk: { | 457 | .prefixed_path = null, |
| 454 | const new = try self.files.addOne(gpa); | 458 | .contents = null, |
| 455 | new.* = .{ | 459 | .max_file_size = null, |
| 456 | .prefixed_path = null, | 460 | .stat = undefined, |
| 457 | .contents = null, | 461 | .bin_digest = undefined, |
| 458 | .max_file_size = null, | 462 | }; |
| 459 | .stat = undefined, | 463 | break :blk new; |
| 460 | .bin_digest = undefined, | | |
| 461 | }; | 464 | }; |
| 462 | break :blk new; | | |
| 463 | }; | | |
| 464 | | 465 | |
| 465 | var iter = mem.tokenize(u8, line, " "); | 466 | var iter = mem.tokenize(u8, line, " "); |
| 466 | const size = iter.next() orelse return error.InvalidFormat; | 467 | const size = iter.next() orelse return error.InvalidFormat; |
| 467 | const inode = iter.next() orelse return error.InvalidFormat; | 468 | const inode = iter.next() orelse return error.InvalidFormat; |
| 468 | const mtime_nsec_str = iter.next() orelse return error.InvalidFormat; | 469 | const mtime_nsec_str = iter.next() orelse return error.InvalidFormat; |
| 469 | const digest_str = iter.next() orelse return error.InvalidFormat; | 470 | const digest_str = iter.next() orelse return error.InvalidFormat; |
| 470 | const prefix_str = iter.next() orelse return error.InvalidFormat; | 471 | const prefix_str = iter.next() orelse return error.InvalidFormat; |
| 471 | const file_path = iter.rest(); | 472 | const file_path = iter.rest(); |
| 472 | | 473 | |
| 473 | cache_hash_file.stat.size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat; | 474 | cache_hash_file.stat.size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat; |
| 474 | cache_hash_file.stat.inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat; | 475 | cache_hash_file.stat.inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat; |
| 475 | cache_hash_file.stat.mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat; | 476 | cache_hash_file.stat.mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat; |
| 476 | _ = fmt.hexToBytes(&cache_hash_file.bin_digest, digest_str) catch return error.InvalidFormat; | 477 | _ = fmt.hexToBytes(&cache_hash_file.bin_digest, digest_str) catch return error.InvalidFormat; |
| 477 | const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat; | 478 | const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat; |
| 478 | if (prefix >= self.cache.prefixes_len) return error.InvalidFormat; | 479 | if (prefix >= self.cache.prefixes_len) return error.InvalidFormat; |
| 479 | | 480 | |
| 480 | if (file_path.len == 0) { | 481 | if (file_path.len == 0) { |
| 481 | return error.InvalidFormat; | | |
| 482 | } | | |
| 483 | if (cache_hash_file.prefixed_path) |pp| { | | |
| 484 | if (pp.prefix != prefix or !mem.eql(u8, file_path, pp.sub_path)) { | | |
| 485 | return error.InvalidFormat; | 482 | return error.InvalidFormat; |
| 486 | } | 483 | } |
| 487 | } | 484 | if (cache_hash_file.prefixed_path) |pp| { |
| 488 | | 485 | if (pp.prefix != prefix or !mem.eql(u8, file_path, pp.sub_path)) { |
| 489 | if (cache_hash_file.prefixed_path == null) { | 486 | return error.InvalidFormat; |
| 490 | cache_hash_file.prefixed_path = .{ | 487 | } |
| 491 | .prefix = prefix, | 488 | } |
| 492 | .sub_path = try gpa.dupe(u8, file_path), | | |
| 493 | }; | | |
| 494 | } | | |
| 495 | | | |
| 496 | const pp = cache_hash_file.prefixed_path.?; | | |
| 497 | const dir = self.cache.prefixes()[pp.prefix].handle; | | |
| 498 | const this_file = dir.openFile(pp.sub_path, .{ .mode = .read_only }) catch |err| switch (err) { | | |
| 499 | error.FileNotFound => { | | |
| 500 | try self.upgradeToExclusiveLock(); | | |
| 501 | return false; | | |
| 502 | }, | | |
| 503 | else => return error.CacheUnavailable, | | |
| 504 | }; | | |
| 505 | defer this_file.close(); | | |
| 506 | | | |
| 507 | const actual_stat = this_file.stat() catch |err| { | | |
| 508 | self.failed_file_index = idx; | | |
| 509 | return err; | | |
| 510 | }; | | |
| 511 | const size_match = actual_stat.size == cache_hash_file.stat.size; | | |
| 512 | const mtime_match = actual_stat.mtime == cache_hash_file.stat.mtime; | | |
| 513 | const inode_match = actual_stat.inode == cache_hash_file.stat.inode; | | |
| 514 | | 489 | |
| 515 | if (!size_match or !mtime_match or !inode_match) { | 490 | if (cache_hash_file.prefixed_path == null) { |
| 516 | self.manifest_dirty = true; | 491 | cache_hash_file.prefixed_path = .{ |
| | 492 | .prefix = prefix, |
| | 493 | .sub_path = try gpa.dupe(u8, file_path), |
| | 494 | }; |
| | 495 | } |
| 517 | | 496 | |
| 518 | cache_hash_file.stat = .{ | 497 | const pp = cache_hash_file.prefixed_path.?; |
| 519 | .size = actual_stat.size, | 498 | const dir = self.cache.prefixes()[pp.prefix].handle; |
| 520 | .mtime = actual_stat.mtime, | 499 | const this_file = dir.openFile(pp.sub_path, .{ .mode = .read_only }) catch |err| switch (err) { |
| 521 | .inode = actual_stat.inode, | 500 | error.FileNotFound => { |
| | 501 | if (try self.upgradeToExclusiveLock()) continue; |
| | 502 | return false; |
| | 503 | }, |
| | 504 | else => return error.CacheUnavailable, |
| 522 | }; | 505 | }; |
| | 506 | defer this_file.close(); |
| 523 | | 507 | |
| 524 | if (self.isProblematicTimestamp(cache_hash_file.stat.mtime)) { | 508 | const actual_stat = this_file.stat() catch |err| { |
| 525 | // The actual file has an unreliable timestamp, force it to be hashed | | |
| 526 | cache_hash_file.stat.mtime = 0; | | |
| 527 | cache_hash_file.stat.inode = 0; | | |
| 528 | } | | |
| 529 | | | |
| 530 | var actual_digest: BinDigest = undefined; | | |
| 531 | hashFile(this_file, &actual_digest) catch |err| { | | |
| 532 | self.failed_file_index = idx; | 509 | self.failed_file_index = idx; |
| 533 | return err; | 510 | return err; |
| 534 | }; | 511 | }; |
| | 512 | const size_match = actual_stat.size == cache_hash_file.stat.size; |
| | 513 | const mtime_match = actual_stat.mtime == cache_hash_file.stat.mtime; |
| | 514 | const inode_match = actual_stat.inode == cache_hash_file.stat.inode; |
| | 515 | |
| | 516 | if (!size_match or !mtime_match or !inode_match) { |
| | 517 | self.manifest_dirty = true; |
| | 518 | |
| | 519 | cache_hash_file.stat = .{ |
| | 520 | .size = actual_stat.size, |
| | 521 | .mtime = actual_stat.mtime, |
| | 522 | .inode = actual_stat.inode, |
| | 523 | }; |
| | 524 | |
| | 525 | if (self.isProblematicTimestamp(cache_hash_file.stat.mtime)) { |
| | 526 | // The actual file has an unreliable timestamp, force it to be hashed |
| | 527 | cache_hash_file.stat.mtime = 0; |
| | 528 | cache_hash_file.stat.inode = 0; |
| | 529 | } |
| | 530 | |
| | 531 | var actual_digest: BinDigest = undefined; |
| | 532 | hashFile(this_file, &actual_digest) catch |err| { |
| | 533 | self.failed_file_index = idx; |
| | 534 | return err; |
| | 535 | }; |
| | 536 | |
| | 537 | if (!mem.eql(u8, &cache_hash_file.bin_digest, &actual_digest)) { |
| | 538 | cache_hash_file.bin_digest = actual_digest; |
| | 539 | // keep going until we have the input file digests |
| | 540 | any_file_changed = true; |
| | 541 | } |
| | 542 | } |
| 535 | | 543 | |
| 536 | if (!mem.eql(u8, &cache_hash_file.bin_digest, &actual_digest)) { | 544 | if (!any_file_changed) { |
| 537 | cache_hash_file.bin_digest = actual_digest; | 545 | self.hash.hasher.update(&cache_hash_file.bin_digest); |
| 538 | // keep going until we have the input file digests | | |
| 539 | any_file_changed = true; | | |
| 540 | } | 546 | } |
| 541 | } | 547 | } |
| 542 | | 548 | |
| 543 | if (!any_file_changed) { | 549 | if (any_file_changed) { |
| 544 | self.hash.hasher.update(&cache_hash_file.bin_digest); | 550 | if (try self.upgradeToExclusiveLock()) continue; |
| | 551 | // cache miss |
| | 552 | // keep the manifest file open |
| | 553 | self.unhit(bin_digest, input_file_count); |
| | 554 | return false; |
| 545 | } | 555 | } |
| 546 | } | | |
| 547 | | 556 | |
| 548 | if (any_file_changed) { | 557 | if (idx < input_file_count) { |
| 549 | // cache miss | 558 | if (try self.upgradeToExclusiveLock()) continue; |
| 550 | // keep the manifest file open | 559 | self.manifest_dirty = true; |
| 551 | self.unhit(bin_digest, input_file_count); | 560 | while (idx < input_file_count) : (idx += 1) { |
| 552 | try self.upgradeToExclusiveLock(); | 561 | const ch_file = &self.files.items[idx]; |
| 553 | return false; | 562 | self.populateFileHash(ch_file) catch |err| { |
| 554 | } | 563 | self.failed_file_index = idx; |
| | 564 | return err; |
| | 565 | }; |
| | 566 | } |
| | 567 | return false; |
| | 568 | } |
| 555 | | 569 | |
| 556 | if (idx < input_file_count) { | 570 | if (self.want_shared_lock) { |
| 557 | self.manifest_dirty = true; | 571 | try self.downgradeToSharedLock(); |
| 558 | while (idx < input_file_count) : (idx += 1) { | | |
| 559 | const ch_file = &self.files.items[idx]; | | |
| 560 | self.populateFileHash(ch_file) catch |err| { | | |
| 561 | self.failed_file_index = idx; | | |
| 562 | return err; | | |
| 563 | }; | | |
| 564 | } | 572 | } |
| 565 | try self.upgradeToExclusiveLock(); | | |
| 566 | return false; | | |
| 567 | } | | |
| 568 | | 573 | |
| 569 | if (self.want_shared_lock) { | 574 | return true; |
| 570 | try self.downgradeToSharedLock(); | | |
| 571 | } | 575 | } |
| 572 | | | |
| 573 | return true; | | |
| 574 | } | 576 | } |
| 575 | | 577 | |
| 576 | pub fn unhit(self: *Manifest, bin_digest: BinDigest, input_file_count: usize) void { | 578 | pub fn unhit(self: *Manifest, bin_digest: BinDigest, input_file_count: usize) void { |
| ... | @@ -867,8 +869,8 @@ pub const Manifest = struct { | ... | @@ -867,8 +869,8 @@ pub const Manifest = struct { |
| 867 | self.have_exclusive_lock = false; | 869 | self.have_exclusive_lock = false; |
| 868 | } | 870 | } |
| 869 | | 871 | |
| 870 | fn upgradeToExclusiveLock(self: *Manifest) !void { | 872 | fn upgradeToExclusiveLock(self: *Manifest) !bool { |
| 871 | if (self.have_exclusive_lock) return; | 873 | if (self.have_exclusive_lock) return false; |
| 872 | assert(self.manifest_file != null); | 874 | assert(self.manifest_file != null); |
| 873 | | 875 | |
| 874 | // WASI does not currently support flock, so we bypass it here. | 876 | // WASI does not currently support flock, so we bypass it here. |
| ... | @@ -882,6 +884,7 @@ pub const Manifest = struct { | ... | @@ -882,6 +884,7 @@ pub const Manifest = struct { |
| 882 | try manifest_file.lock(.Exclusive); | 884 | try manifest_file.lock(.Exclusive); |
| 883 | } | 885 | } |
| 884 | self.have_exclusive_lock = true; | 886 | self.have_exclusive_lock = true; |
| | 887 | return true; |
| 885 | } | 888 | } |
| 886 | | 889 | |
| 887 | /// Obtain only the data needed to maintain a lock on the manifest file. | 890 | /// Obtain only the data needed to maintain a lock on the manifest file. |