Skip to content

Commit e3cf9d1

Browse files
jacobly0andrewrk
authored andcommitted
Module: rewrite zir caching logic
Multiple processes can sit waiting for the exclusive lock at the same time, so we want to recheck whether it needs to be updated whenever we get an exclusive lock. This also fixes a race condition between one process truncating the cache file and another process reading it without atomic locking.
1 parent 6fc1621 commit e3cf9d1

File tree

2 files changed

+62
-67
lines changed

2 files changed

+62
-67
lines changed

src/Module.zig

+62-65
Original file line numberDiff line numberDiff line change
@@ -3538,44 +3538,61 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
35383538
const cache_directory = if (want_local_cache) mod.local_zir_cache else mod.global_zir_cache;
35393539
const zir_dir = cache_directory.handle;
35403540

3541-
var cache_file: ?std.fs.File = null;
3542-
defer if (cache_file) |f| f.close();
3543-
35443541
// Determine whether we need to reload the file from disk and redo parsing and AstGen.
3545-
switch (file.status) {
3546-
.never_loaded, .retryable_failure => cached: {
3542+
var lock: std.fs.File.Lock = switch (file.status) {
3543+
.never_loaded, .retryable_failure => lock: {
35473544
// First, load the cached ZIR code, if any.
35483545
log.debug("AstGen checking cache: {s} (local={}, digest={s})", .{
35493546
file.sub_file_path, want_local_cache, &digest,
35503547
});
35513548

3552-
// We ask for a lock in order to coordinate with other zig processes.
3553-
// If another process is already working on this file, we will get the cached
3554-
// version. Likewise if we're working on AstGen and another process asks for
3555-
// the cached file, they'll get it.
3556-
cache_file = zir_dir.openFile(&digest, .{ .lock = .Shared }) catch |err| switch (err) {
3557-
error.PathAlreadyExists => unreachable, // opening for reading
3558-
error.NoSpaceLeft => unreachable, // opening for reading
3559-
error.NotDir => unreachable, // no dir components
3560-
error.InvalidUtf8 => unreachable, // it's a hex encoded name
3561-
error.BadPathName => unreachable, // it's a hex encoded name
3562-
error.NameTooLong => unreachable, // it's a fixed size name
3563-
error.PipeBusy => unreachable, // it's not a pipe
3564-
error.WouldBlock => unreachable, // not asking for non-blocking I/O
3565-
3566-
error.SymLinkLoop,
3567-
error.FileNotFound,
3568-
error.Unexpected,
3569-
=> break :cached,
3570-
3571-
else => |e| return e, // Retryable errors are handled at callsite.
3572-
};
3549+
break :lock .Shared;
3550+
},
3551+
.parse_failure, .astgen_failure, .success_zir => lock: {
3552+
const unchanged_metadata =
3553+
stat.size == file.stat.size and
3554+
stat.mtime == file.stat.mtime and
3555+
stat.inode == file.stat.inode;
3556+
3557+
if (unchanged_metadata) {
3558+
log.debug("unmodified metadata of file: {s}", .{file.sub_file_path});
3559+
return;
3560+
}
3561+
3562+
log.debug("metadata changed: {s}", .{file.sub_file_path});
3563+
3564+
break :lock .Exclusive;
3565+
},
3566+
};
3567+
3568+
// We ask for a lock in order to coordinate with other zig processes.
3569+
// If another process is already working on this file, we will get the cached
3570+
// version. Likewise if we're working on AstGen and another process asks for
3571+
// the cached file, they'll get it.
3572+
const cache_file = zir_dir.createFile(&digest, .{
3573+
.read = true,
3574+
.truncate = false,
3575+
.lock = lock,
3576+
}) catch |err| switch (err) {
3577+
error.NotDir => unreachable, // no dir components
3578+
error.InvalidUtf8 => unreachable, // it's a hex encoded name
3579+
error.BadPathName => unreachable, // it's a hex encoded name
3580+
error.NameTooLong => unreachable, // it's a fixed size name
3581+
error.PipeBusy => unreachable, // it's not a pipe
3582+
error.WouldBlock => unreachable, // not asking for non-blocking I/O
3583+
error.FileNotFound => unreachable, // no dir components
3584+
3585+
else => |e| return e, // Retryable errors are handled at callsite.
3586+
};
3587+
defer cache_file.close();
35733588

3589+
while (true) {
3590+
update: {
35743591
// First we read the header to determine the lengths of arrays.
3575-
const header = cache_file.?.reader().readStruct(Zir.Header) catch |err| switch (err) {
3592+
const header = cache_file.reader().readStruct(Zir.Header) catch |err| switch (err) {
35763593
// This can happen if Zig bails out of this function between creating
35773594
// the cached file and writing it.
3578-
error.EndOfStream => break :cached,
3595+
error.EndOfStream => break :update,
35793596
else => |e| return e,
35803597
};
35813598
const unchanged_metadata =
@@ -3585,7 +3602,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
35853602

35863603
if (!unchanged_metadata) {
35873604
log.debug("AstGen cache stale: {s}", .{file.sub_file_path});
3588-
break :cached;
3605+
break :update;
35893606
}
35903607
log.debug("AstGen cache hit: {s} instructions_len={d}", .{
35913608
file.sub_file_path, header.instructions_len,
@@ -3637,13 +3654,13 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
36373654
.iov_len = header.extra_len * 4,
36383655
},
36393656
};
3640-
const amt_read = try cache_file.?.readvAll(&iovecs);
3657+
const amt_read = try cache_file.readvAll(&iovecs);
36413658
const amt_expected = zir.instructions.len * 9 +
36423659
zir.string_bytes.len +
36433660
zir.extra.len * 4;
36443661
if (amt_read != amt_expected) {
36453662
log.warn("unexpected EOF reading cached ZIR for {s}", .{file.sub_file_path});
3646-
break :cached;
3663+
break :update;
36473664
}
36483665
if (data_has_safety_tag) {
36493666
const tags = zir.instructions.items(.tag);
@@ -3679,42 +3696,22 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
36793696
return error.AnalysisFail;
36803697
}
36813698
return;
3682-
},
3683-
.parse_failure, .astgen_failure, .success_zir => {
3684-
const unchanged_metadata =
3685-
stat.size == file.stat.size and
3686-
stat.mtime == file.stat.mtime and
3687-
stat.inode == file.stat.inode;
3688-
3689-
if (unchanged_metadata) {
3690-
log.debug("unmodified metadata of file: {s}", .{file.sub_file_path});
3691-
return;
3692-
}
3699+
}
36933700

3694-
log.debug("metadata changed: {s}", .{file.sub_file_path});
3695-
},
3696-
}
3697-
if (cache_file) |f| {
3698-
f.close();
3699-
cache_file = null;
3701+
// If we already have the exclusive lock then it is our job to update.
3702+
if (builtin.os.tag == .wasi or lock == .Exclusive) break;
3703+
// Otherwise, unlock to give someone a chance to get the exclusive lock
3704+
// and then upgrade to an exclusive lock.
3705+
cache_file.unlock();
3706+
lock = .Exclusive;
3707+
try cache_file.lock(lock);
37003708
}
3701-
cache_file = zir_dir.createFile(&digest, .{ .lock = .Exclusive }) catch |err| switch (err) {
3702-
error.NotDir => unreachable, // no dir components
3703-
error.InvalidUtf8 => unreachable, // it's a hex encoded name
3704-
error.BadPathName => unreachable, // it's a hex encoded name
3705-
error.NameTooLong => unreachable, // it's a fixed size name
3706-
error.PipeBusy => unreachable, // it's not a pipe
3707-
error.WouldBlock => unreachable, // not asking for non-blocking I/O
3708-
error.FileNotFound => unreachable, // no dir components
37093709

3710-
else => |e| {
3711-
const pkg_path = file.pkg.root_src_directory.path orelse ".";
3712-
const cache_path = cache_directory.path orelse ".";
3713-
log.warn("unable to save cached ZIR code for {s}/{s} to {s}/{s}: {s}", .{
3714-
pkg_path, file.sub_file_path, cache_path, &digest, @errorName(e),
3715-
});
3716-
return;
3717-
},
3710+
// The cache is definitely stale so delete the contents to avoid an underwrite later.
3711+
cache_file.setEndPos(0) catch |err| switch (err) {
3712+
error.FileTooBig => unreachable, // 0 is not too big
3713+
3714+
else => |e| return e,
37183715
};
37193716

37203717
mod.lockAndClearFileCompileError(file);
@@ -3871,7 +3868,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
38713868
.iov_len = file.zir.extra.len * 4,
38723869
},
38733870
};
3874-
cache_file.?.writevAll(&iovecs) catch |err| {
3871+
cache_file.writevAll(&iovecs) catch |err| {
38753872
const pkg_path = file.pkg.root_src_directory.path orelse ".";
38763873
const cache_path = cache_directory.path orelse ".";
38773874
log.warn("unable to write cached ZIR code for {s}/{s} to {s}/{s}: {s}", .{

stage1/wasi.c

-2
Original file line numberDiff line numberDiff line change
@@ -497,8 +497,6 @@ uint32_t wasi_snapshot_preview1_fd_read(uint32_t fd, uint32_t iovs, uint32_t iov
497497
size_t read_size = 0;
498498
if (fds[fd].stream != NULL)
499499
read_size = fread(&m[iovs_ptr[i].ptr], 1, iovs_ptr[i].len, fds[fd].stream);
500-
else
501-
panic("unimplemented");
502500
size += read_size;
503501
if (read_size < iovs_ptr[i].len) break;
504502
}

0 commit comments

Comments
 (0)