| ... | ... | @@ -12,6 +12,14 @@ const mem = std.mem; |
| 12 | 12 | const fmt = std.fmt; |
| 13 | 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 | 23 | /// Be sure to call `Manifest.deinit` after successful initialization. |
| 16 | 24 | pub fn obtain(cache: *const Cache) Manifest { |
| 17 | 25 | return Manifest{ |
| ... | ... | @@ -160,8 +168,15 @@ pub const HashHelper = struct { |
| 160 | 168 | |
| 161 | 169 | pub const Lock = struct { |
| 162 | 170 | manifest_file: fs.File, |
| 171 | debug_bin_digest: DebugBinDigest, |
| 163 | 172 | |
| 164 | 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 | 180 | lock.manifest_file.close(); |
| 166 | 181 | lock.* = undefined; |
| 167 | 182 | } |
| ... | ... | @@ -178,6 +193,7 @@ pub const Manifest = struct { |
| 178 | 193 | manifest_dirty: bool, |
| 179 | 194 | files: std.ArrayListUnmanaged(File) = .{}, |
| 180 | 195 | hex_digest: [hex_digest_len]u8, |
| 196 | bin_digest: DebugBinDigest = undefined, |
| 181 | 197 | |
| 182 | 198 | /// Add a file as a dependency of process being cached. When `hit` is |
| 183 | 199 | /// called, the file's contents will be checked to ensure that it matches |
| ... | ... | @@ -245,6 +261,23 @@ pub const Manifest = struct { |
| 245 | 261 | var bin_digest: BinDigest = undefined; |
| 246 | 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 | 281 | _ = std.fmt.bufPrint(&self.hex_digest, "{x}", .{bin_digest}) catch unreachable; |
| 249 | 282 | |
| 250 | 283 | self.hash.hasher = hasher_init; |
| ... | ... | @@ -572,7 +605,10 @@ pub const Manifest = struct { |
| 572 | 605 | pub fn toOwnedLock(self: *Manifest) Lock { |
| 573 | 606 | const manifest_file = self.manifest_file.?; |
| 574 | 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 | 614 | /// Releases the manifest file and frees any memory the Manifest was using. |