Commit a4418a8fd6

Casey Banner <kcbanner@gmail.com>
2022-12-07 05:15:54
cache: Fix LockViolation during C compilation paths (#13591)
- C compilation flows didn't hold an exclusive lock on the cache manifest file when writing to it in all cases - On windows, explicitly unlock the file lock before closing it
1 parent bb957a9
src/Cache.zig
@@ -170,6 +170,12 @@ pub const Lock = struct {
     manifest_file: fs.File,
 
     pub fn release(lock: *Lock) void {
+        if (builtin.os.tag == .windows) {
+            // Windows does not guarantee that locks are immediately unlocked when
+            // the file handle is closed. See LockFileEx documentation.
+            lock.manifest_file.unlock();
+        }
+
         lock.manifest_file.close();
         lock.* = undefined;
     }
@@ -480,7 +486,10 @@ pub const Manifest = struct {
             return false;
         }
 
-        try self.downgradeToSharedLock();
+        if (self.want_shared_lock) {
+            try self.downgradeToSharedLock();
+        }
+
         return true;
     }
 
@@ -770,11 +779,13 @@ pub const Manifest = struct {
             const manifest_file = self.manifest_file.?;
             try manifest_file.downgradeLock();
         }
+
         self.have_exclusive_lock = false;
     }
 
     fn upgradeToExclusiveLock(self: *Manifest) !void {
         if (self.have_exclusive_lock) return;
+        assert(self.manifest_file != null);
 
         // WASI does not currently support flock, so we bypass it here.
         // TODO: If/when flock is supported on WASI, this check should be removed.
@@ -796,6 +807,7 @@ pub const Manifest = struct {
         const lock: Lock = .{
             .manifest_file = self.manifest_file.?,
         };
+
         self.manifest_file = null;
         return lock;
     }
@@ -805,6 +817,11 @@ pub const Manifest = struct {
     /// Don't forget to call `writeManifest` before this!
     pub fn deinit(self: *Manifest) void {
         if (self.manifest_file) |file| {
+            if (builtin.os.tag == .windows) {
+                // See Lock.release for why this is required on Windows
+                file.unlock();
+            }
+
             file.close();
         }
         for (self.files.items) |*file| {
src/Compilation.zig
@@ -3580,6 +3580,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult {
     const cimport_zig_basename = "cimport.zig";
 
     var man = comp.obtainCObjectCacheManifest();
+    man.want_shared_lock = false;
     defer man.deinit();
 
     const use_stage1 = build_options.have_stage1 and comp.bin_file.options.use_stage1;
@@ -3697,6 +3698,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult {
     // possible we had a hit and the manifest is dirty, for example if the file mtime changed but
     // the contents were the same, we hit the cache but the manifest is dirty and we need to update
     // it to prevent doing a full file content comparison the next time around.
+    man.want_shared_lock = true;
     man.writeManifest() catch |err| {
         log.warn("failed to write cache manifest for C import: {s}", .{@errorName(err)});
     };
@@ -3871,6 +3873,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
     }
 
     var man = comp.obtainCObjectCacheManifest();
+    man.want_shared_lock = false;
     defer man.deinit();
 
     man.hash.add(comp.clang_preprocessor_mode);
@@ -4164,6 +4167,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
     // possible we had a hit and the manifest is dirty, for example if the file mtime changed but
     // the contents were the same, we hit the cache but the manifest is dirty and we need to update
     // it to prevent doing a full file content comparison the next time around.
+    man.want_shared_lock = true;
     man.writeManifest() catch |err| {
         log.warn("failed to write cache manifest when compiling '{s}': {s}", .{ c_object.src.src_path, @errorName(err) });
     };
src/main.zig
@@ -3429,6 +3429,7 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool, stage
     const translated_zig_basename = try std.fmt.allocPrint(arena, "{s}.zig", .{comp.bin_file.options.root_name});
 
     var man: Cache.Manifest = comp.obtainCObjectCacheManifest();
+    man.want_shared_lock = false;
     defer if (enable_cache) man.deinit();
 
     man.hash.add(@as(u16, 0xb945)); // Random number to distinguish translate-c from compiling C objects