authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2023-05-10 01:52:33-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2023-05-11 00:55:40-04:00
logfae6290387850bfddbe4e9c1255750a65568b763
tree3812c9ca4ee0436fe3dd77b1d7b237313fb666bb
parent2d2d79a05b8423c7638348dc5a89793c1e0cafce

Cache: fix race condition

When checking a cache entry with no input files for a hit, if `createFile` returned `error.WouldBlock` we would forget about the fact that the file has been created, and all future checks will assume that a cache hit has happened, even though one never has or does, leading to rare `FileNotFound` errors trying the access the protected files. This fix works by writing an extra byte to the manifest file to distinguish hits and misses when there no input files to write.

1 files changed, 48 insertions(+), 76 deletions(-)

lib/std/Build/Cache.zig+48-76
......@@ -145,6 +145,8 @@ pub const bin_digest_len = 16;
145145pub const hex_digest_len = bin_digest_len * 2;
146146pub const BinDigest = [bin_digest_len]u8;
147147
148/// This is currently just an arbitrary non-empty string that can't match another manifest line.
149const manifest_header = "0";
148150const manifest_file_size_max = 50 * 1024 * 1024;
149151
150152/// The type used for hashing file contents. Currently, this is SipHash128(1, 3), because it
......@@ -152,8 +154,15 @@ const manifest_file_size_max = 50 * 1024 * 1024;
152154/// fastest options right now.
153155pub const Hasher = crypto.auth.siphash.SipHash128(1, 3);
154156
155/// Initial state, that can be copied.
156pub const hasher_init: Hasher = Hasher.init(&[_]u8{0} ** Hasher.key_length);
157/// Initial state with random bytes, that can be copied.
158/// Refresh this with new random bytes when the manifest
159/// format is modified in a non-backwards-compatible way.
160pub const hasher_init: Hasher = Hasher.init(&[_]u8{
161 0x33, 0x52, 0xa2, 0x84,
162 0xcf, 0x17, 0x56, 0x57,
163 0x01, 0xbb, 0xcd, 0xe4,
164 0x77, 0xd6, 0xf0, 0x60,
165});
157166
158167pub const File = struct {
159168 prefixed_path: ?PrefixedPath,
......@@ -391,72 +400,29 @@ pub const Manifest = struct {
391400 @memcpy(manifest_file_path[0..self.hex_digest.len], &self.hex_digest);
392401 manifest_file_path[hex_digest_len..][0..ext.len].* = ext.*;
393402
394 if (self.files.items.len == 0) {
395 // If there are no file inputs, we check if the manifest file exists instead of
396 // comparing the hashes on the files used for the cached item
397 while (true) {
398 if (self.cache.manifest_dir.openFile(&manifest_file_path, .{
399 .mode = .read_write,
400 .lock = .Exclusive,
401 .lock_nonblocking = self.want_shared_lock,
402 })) |manifest_file| {
403 self.manifest_file = manifest_file;
404 self.have_exclusive_lock = true;
405 break;
406 } else |open_err| switch (open_err) {
407 error.WouldBlock => {
408 self.manifest_file = try self.cache.manifest_dir.openFile(&manifest_file_path, .{
409 .lock = .Shared,
410 });
411 break;
412 },
413 error.FileNotFound => {
414 if (self.cache.manifest_dir.createFile(&manifest_file_path, .{
415 .read = true,
416 .truncate = false,
417 .lock = .Exclusive,
418 .lock_nonblocking = self.want_shared_lock,
419 })) |manifest_file| {
420 self.manifest_file = manifest_file;
421 self.manifest_dirty = true;
422 self.have_exclusive_lock = true;
423 return false; // cache miss; exclusive lock already held
424 } else |err| switch (err) {
425 // There are no dir components, so you would think
426 // that this was unreachable, however we have
427 // observed on macOS two processes racing to do
428 // openat() with O_CREAT manifest in ENOENT.
429 error.WouldBlock, error.FileNotFound => continue,
430 else => |e| return e,
431 }
432 },
433 else => |e| return e,
434 }
435 }
436 } else {
437 while (true) {
438 if (self.cache.manifest_dir.createFile(&manifest_file_path, .{
439 .read = true,
440 .truncate = false,
441 .lock = .Exclusive,
442 .lock_nonblocking = self.want_shared_lock,
443 })) |manifest_file| {
444 self.manifest_file = manifest_file;
445 self.have_exclusive_lock = true;
403 while (true) {
404 if (self.cache.manifest_dir.createFile(&manifest_file_path, .{
405 .read = true,
406 .truncate = false,
407 .lock = .Exclusive,
408 .lock_nonblocking = self.want_shared_lock,
409 })) |manifest_file| {
410 self.manifest_file = manifest_file;
411 self.have_exclusive_lock = true;
412 break;
413 } else |err| switch (err) {
414 error.WouldBlock => {
415 self.manifest_file = try self.cache.manifest_dir.openFile(&manifest_file_path, .{
416 .mode = .read_write,
417 .lock = .Shared,
418 });
446419 break;
447 } else |err| switch (err) {
448 error.WouldBlock => {
449 self.manifest_file = try self.cache.manifest_dir.openFile(&manifest_file_path, .{
450 .lock = .Shared,
451 });
452 break;
453 },
454 // There are no dir components, so you would think that this was
455 // unreachable, however we have observed on macOS two processes racing
456 // to do openat() with O_CREAT manifest in ENOENT.
457 error.FileNotFound => continue,
458 else => |e| return e,
459 }
420 },
421 // There are no dir components, so you would think that this was
422 // unreachable, however we have observed on macOS two processes racing
423 // to do openat() with O_CREAT manifest in ENOENT.
424 error.FileNotFound => continue,
425 else => |e| return e,
460426 }
461427 }
462428
......@@ -469,6 +435,18 @@ pub const Manifest = struct {
469435 var any_file_changed = false;
470436 var line_iter = mem.tokenize(u8, file_contents, "\n");
471437 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 };
446 }
447 try self.upgradeToExclusiveLock();
448 return false;
449 }
472450 while (line_iter.next()) |line| {
473451 defer idx += 1;
474452
......@@ -854,19 +832,13 @@ pub const Manifest = struct {
854832 defer contents.deinit();
855833
856834 const writer = contents.writer();
857 var encoded_digest: [hex_digest_len]u8 = undefined;
858
835 try writer.writeAll(manifest_header ++ "\n");
859836 for (self.files.items) |file| {
860 _ = fmt.bufPrint(
861 &encoded_digest,
862 "{s}",
863 .{fmt.fmtSliceHexLower(&file.bin_digest)},
864 ) catch unreachable;
865 try writer.print("{d} {d} {d} {s} {d} {s}\n", .{
837 try writer.print("{d} {d} {d} {} {d} {s}\n", .{
866838 file.stat.size,
867839 file.stat.inode,
868840 file.stat.mtime,
869 &encoded_digest,
841 fmt.fmtSliceHexLower(&file.bin_digest),
870842 file.prefixed_path.?.prefix,
871843 file.prefixed_path.?.sub_path,
872844 });