@@ -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}" , .{
0 commit comments