| ... | @@ -12,6 +12,14 @@ const mem = std.mem; | ... | @@ -12,6 +12,14 @@ const mem = std.mem; |
| 12 | const fmt = std.fmt; | 12 | const fmt = std.fmt; |
| 13 | const Allocator = std.mem.Allocator; | 13 | const Allocator = std.mem.Allocator; |
| 14 | | 14 | |
| | 15 | /// Process-scoped map keeping track of all locked Cache hashes, to detect deadlocks. |
| | 16 | /// The plan is to enable this for debug builds only, but for now we enable |
| | 17 | /// it always to catch a deadlock. |
| | 18 | var all_cache_digest_set: std.AutoHashMapUnmanaged(BinDigest, void) = .{}; |
| | 19 | var all_cache_digest_lock: std.Mutex = .{}; |
| | 20 | const want_debug_deadlock = true; // TODO change this for release builds |
| | 21 | const DebugBinDigest = if (want_debug_deadlock) BinDigest else void; |
| | 22 | |
| 15 | /// Be sure to call `Manifest.deinit` after successful initialization. | 23 | /// Be sure to call `Manifest.deinit` after successful initialization. |
| 16 | pub fn obtain(cache: *const Cache) Manifest { | 24 | pub fn obtain(cache: *const Cache) Manifest { |
| 17 | return Manifest{ | 25 | return Manifest{ |
| ... | @@ -160,8 +168,15 @@ pub const HashHelper = struct { | ... | @@ -160,8 +168,15 @@ pub const HashHelper = struct { |
| 160 | | 168 | |
| 161 | pub const Lock = struct { | 169 | pub const Lock = struct { |
| 162 | manifest_file: fs.File, | 170 | manifest_file: fs.File, |
| | 171 | debug_bin_digest: DebugBinDigest, |
| 163 | | 172 | |
| 164 | pub fn release(lock: *Lock) void { | 173 | pub fn release(lock: *Lock) void { |
| | 174 | if (want_debug_deadlock) { |
| | 175 | const held = all_cache_digest_lock.acquire(); |
| | 176 | defer held.release(); |
| | 177 | |
| | 178 | all_cache_digest_set.removeAssertDiscard(lock.debug_bin_digest); |
| | 179 | } |
| 165 | lock.manifest_file.close(); | 180 | lock.manifest_file.close(); |
| 166 | lock.* = undefined; | 181 | lock.* = undefined; |
| 167 | } | 182 | } |
| ... | @@ -178,6 +193,7 @@ pub const Manifest = struct { | ... | @@ -178,6 +193,7 @@ pub const Manifest = struct { |
| 178 | manifest_dirty: bool, | 193 | manifest_dirty: bool, |
| 179 | files: std.ArrayListUnmanaged(File) = .{}, | 194 | files: std.ArrayListUnmanaged(File) = .{}, |
| 180 | hex_digest: [hex_digest_len]u8, | 195 | hex_digest: [hex_digest_len]u8, |
| | 196 | bin_digest: DebugBinDigest = undefined, |
| 181 | | 197 | |
| 182 | /// Add a file as a dependency of process being cached. When `hit` is | 198 | /// Add a file as a dependency of process being cached. When `hit` is |
| 183 | /// called, the file's contents will be checked to ensure that it matches | 199 | /// called, the file's contents will be checked to ensure that it matches |
| ... | @@ -245,6 +261,23 @@ pub const Manifest = struct { | ... | @@ -245,6 +261,23 @@ pub const Manifest = struct { |
| 245 | var bin_digest: BinDigest = undefined; | 261 | var bin_digest: BinDigest = undefined; |
| 246 | self.hash.hasher.final(&bin_digest); | 262 | self.hash.hasher.final(&bin_digest); |
| 247 | | 263 | |
| | 264 | if (want_debug_deadlock) { |
| | 265 | self.bin_digest = bin_digest; |
| | 266 | |
| | 267 | const held = all_cache_digest_lock.acquire(); |
| | 268 | defer held.release(); |
| | 269 | |
| | 270 | const gop = try all_cache_digest_set.getOrPut(self.cache.gpa, bin_digest); |
| | 271 | if (gop.found_existing) { |
| | 272 | std.debug.print("Cache deadlock detected in Cache.hit. Manifest has {d} files:\n", .{self.files.items.len}); |
| | 273 | for (self.files.items) |file| { |
| | 274 | const p: []const u8 = file.path orelse "(null)"; |
| | 275 | std.debug.print(" file: {s}\n", .{p}); |
| | 276 | } |
| | 277 | @panic("Cache deadlock detected"); |
| | 278 | } |
| | 279 | } |
| | 280 | |
| 248 | _ = std.fmt.bufPrint(&self.hex_digest, "{x}", .{bin_digest}) catch unreachable; | 281 | _ = std.fmt.bufPrint(&self.hex_digest, "{x}", .{bin_digest}) catch unreachable; |
| 249 | | 282 | |
| 250 | self.hash.hasher = hasher_init; | 283 | self.hash.hasher = hasher_init; |
| ... | @@ -572,7 +605,10 @@ pub const Manifest = struct { | ... | @@ -572,7 +605,10 @@ pub const Manifest = struct { |
| 572 | pub fn toOwnedLock(self: *Manifest) Lock { | 605 | pub fn toOwnedLock(self: *Manifest) Lock { |
| 573 | const manifest_file = self.manifest_file.?; | 606 | const manifest_file = self.manifest_file.?; |
| 574 | self.manifest_file = null; | 607 | self.manifest_file = null; |
| 575 | return Lock{ .manifest_file = manifest_file }; | 608 | return Lock{ |
| | 609 | .manifest_file = manifest_file, |
| | 610 | .debug_bin_digest = self.bin_digest, |
| | 611 | }; |
| 576 | } | 612 | } |
| 577 | | 613 | |
| 578 | /// Releases the manifest file and frees any memory the Manifest was using. | 614 | /// Releases the manifest file and frees any memory the Manifest was using. |