authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2023-05-11 00:51:41-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2023-05-11 00:55:40-04:00
log6e292f66db8e7b85ea8b116872b3ca074db7885a
tree77c5406e95adf470bc7cdd473cc683c1b1c6f3ab
parentfae6290387850bfddbe4e9c1255750a65568b763

Cache: fix unnecessary cache misses

With the old logic, it was possible for a bunch of processes to queue up to update a cache entry, and then each to do so one at a time. Now, it rechecks whether there still a cache miss or another process has completed the work in the interim.

1 files changed, 126 insertions(+), 123 deletions(-)

lib/std/Build/Cache.zig+126-123
......@@ -428,149 +428,151 @@ pub const Manifest = struct {
428428
429429 self.want_refresh_timestamp = true;
430430
431 const file_contents = try self.manifest_file.?.reader().readAllAlloc(gpa, manifest_file_size_max);
432 defer gpa.free(file_contents);
433
434 const input_file_count = self.files.items.len;
435 var any_file_changed = false;
436 var line_iter = mem.tokenize(u8, file_contents, "\n");
437 var idx: usize = 0;
438 if (if (line_iter.next()) |line| !std.mem.eql(u8, line, manifest_header) else true) {
439 self.manifest_dirty = true;
440 while (idx < input_file_count) : (idx += 1) {
441 const ch_file = &self.files.items[idx];
442 self.populateFileHash(ch_file) catch |err| {
443 self.failed_file_index = idx;
444 return err;
445 };
431 while (true) {
432 const file_contents = try self.manifest_file.?.reader().readAllAlloc(gpa, manifest_file_size_max);
433 defer gpa.free(file_contents);
434
435 const input_file_count = self.files.items.len;
436 var any_file_changed = false;
437 var line_iter = mem.tokenize(u8, file_contents, "\n");
438 var idx: usize = 0;
439 if (if (line_iter.next()) |line| !std.mem.eql(u8, line, manifest_header) else true) {
440 if (try self.upgradeToExclusiveLock()) continue;
441 self.manifest_dirty = true;
442 while (idx < input_file_count) : (idx += 1) {
443 const ch_file = &self.files.items[idx];
444 self.populateFileHash(ch_file) catch |err| {
445 self.failed_file_index = idx;
446 return err;
447 };
448 }
449 return false;
446450 }
447 try self.upgradeToExclusiveLock();
448 return false;
449 }
450 while (line_iter.next()) |line| {
451 defer idx += 1;
452
453 const cache_hash_file = if (idx < input_file_count) &self.files.items[idx] else blk: {
454 const new = try self.files.addOne(gpa);
455 new.* = .{
456 .prefixed_path = null,
457 .contents = null,
458 .max_file_size = null,
459 .stat = undefined,
460 .bin_digest = undefined,
451 while (line_iter.next()) |line| {
452 defer idx += 1;
453
454 const cache_hash_file = if (idx < input_file_count) &self.files.items[idx] else blk: {
455 const new = try self.files.addOne(gpa);
456 new.* = .{
457 .prefixed_path = null,
458 .contents = null,
459 .max_file_size = null,
460 .stat = undefined,
461 .bin_digest = undefined,
462 };
463 break :blk new;
461464 };
462 break :blk new;
463 };
464465
465 var iter = mem.tokenize(u8, line, " ");
466 const size = iter.next() orelse return error.InvalidFormat;
467 const inode = iter.next() orelse return error.InvalidFormat;
468 const mtime_nsec_str = iter.next() orelse return error.InvalidFormat;
469 const digest_str = iter.next() orelse return error.InvalidFormat;
470 const prefix_str = iter.next() orelse return error.InvalidFormat;
471 const file_path = iter.rest();
472
473 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.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 const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat;
478 if (prefix >= self.cache.prefixes_len) return error.InvalidFormat;
479
480 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)) {
466 var iter = mem.tokenize(u8, line, " ");
467 const size = iter.next() orelse return error.InvalidFormat;
468 const inode = iter.next() orelse return error.InvalidFormat;
469 const mtime_nsec_str = iter.next() orelse return error.InvalidFormat;
470 const digest_str = iter.next() orelse return error.InvalidFormat;
471 const prefix_str = iter.next() orelse return error.InvalidFormat;
472 const file_path = iter.rest();
473
474 cache_hash_file.stat.size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat;
475 cache_hash_file.stat.inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat;
476 cache_hash_file.stat.mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat;
477 _ = fmt.hexToBytes(&cache_hash_file.bin_digest, digest_str) catch return error.InvalidFormat;
478 const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat;
479 if (prefix >= self.cache.prefixes_len) return error.InvalidFormat;
480
481 if (file_path.len == 0) {
485482 return error.InvalidFormat;
486483 }
487 }
488
489 if (cache_hash_file.prefixed_path == null) {
490 cache_hash_file.prefixed_path = .{
491 .prefix = prefix,
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;
484 if (cache_hash_file.prefixed_path) |pp| {
485 if (pp.prefix != prefix or !mem.eql(u8, file_path, pp.sub_path)) {
486 return error.InvalidFormat;
487 }
488 }
514489
515 if (!size_match or !mtime_match or !inode_match) {
516 self.manifest_dirty = true;
490 if (cache_hash_file.prefixed_path == null) {
491 cache_hash_file.prefixed_path = .{
492 .prefix = prefix,
493 .sub_path = try gpa.dupe(u8, file_path),
494 };
495 }
517496
518 cache_hash_file.stat = .{
519 .size = actual_stat.size,
520 .mtime = actual_stat.mtime,
521 .inode = actual_stat.inode,
497 const pp = cache_hash_file.prefixed_path.?;
498 const dir = self.cache.prefixes()[pp.prefix].handle;
499 const this_file = dir.openFile(pp.sub_path, .{ .mode = .read_only }) catch |err| switch (err) {
500 error.FileNotFound => {
501 if (try self.upgradeToExclusiveLock()) continue;
502 return false;
503 },
504 else => return error.CacheUnavailable,
522505 };
506 defer this_file.close();
523507
524 if (self.isProblematicTimestamp(cache_hash_file.stat.mtime)) {
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| {
508 const actual_stat = this_file.stat() catch |err| {
532509 self.failed_file_index = idx;
533510 return err;
534511 };
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 }
535543
536 if (!mem.eql(u8, &cache_hash_file.bin_digest, &actual_digest)) {
537 cache_hash_file.bin_digest = actual_digest;
538 // keep going until we have the input file digests
539 any_file_changed = true;
544 if (!any_file_changed) {
545 self.hash.hasher.update(&cache_hash_file.bin_digest);
540546 }
541547 }
542548
543 if (!any_file_changed) {
544 self.hash.hasher.update(&cache_hash_file.bin_digest);
549 if (any_file_changed) {
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;
545555 }
546 }
547556
548 if (any_file_changed) {
549 // cache miss
550 // keep the manifest file open
551 self.unhit(bin_digest, input_file_count);
552 try self.upgradeToExclusiveLock();
553 return false;
554 }
557 if (idx < input_file_count) {
558 if (try self.upgradeToExclusiveLock()) continue;
559 self.manifest_dirty = true;
560 while (idx < input_file_count) : (idx += 1) {
561 const ch_file = &self.files.items[idx];
562 self.populateFileHash(ch_file) catch |err| {
563 self.failed_file_index = idx;
564 return err;
565 };
566 }
567 return false;
568 }
555569
556 if (idx < input_file_count) {
557 self.manifest_dirty = true;
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 };
570 if (self.want_shared_lock) {
571 try self.downgradeToSharedLock();
564572 }
565 try self.upgradeToExclusiveLock();
566 return false;
567 }
568573
569 if (self.want_shared_lock) {
570 try self.downgradeToSharedLock();
574 return true;
571575 }
572
573 return true;
574576 }
575577
576578 pub fn unhit(self: *Manifest, bin_digest: BinDigest, input_file_count: usize) void {
......@@ -867,8 +869,8 @@ pub const Manifest = struct {
867869 self.have_exclusive_lock = false;
868870 }
869871
870 fn upgradeToExclusiveLock(self: *Manifest) !void {
871 if (self.have_exclusive_lock) return;
872 fn upgradeToExclusiveLock(self: *Manifest) !bool {
873 if (self.have_exclusive_lock) return false;
872874 assert(self.manifest_file != null);
873875
874876 // WASI does not currently support flock, so we bypass it here.
......@@ -882,6 +884,7 @@ pub const Manifest = struct {
882884 try manifest_file.lock(.Exclusive);
883885 }
884886 self.have_exclusive_lock = true;
887 return true;
885888 }
886889
887890 /// Obtain only the data needed to maintain a lock on the manifest file.