diff --git a/lib/std/array_hash_map.zig b/lib/std/array_hash_map.zig index b688551017f0cb945d67c0fb35d038ec332d7d78..2152796e1cc66d10dfdcf4fae07a2f8de840e643 100644 --- a/lib/std/array_hash_map.zig +++ b/lib/std/array_hash_map.zig @@ -200,17 +200,18 @@ pub fn Custom( /// cause an existing key or value pointer to become invalidated will /// instead trigger an assertion. /// - /// An additional call to `lockPointers` in such state also triggers an - /// assertion. + /// `lockPointers` may be called multiple times. This allows multiple + /// independent users of the hash map to keep it locked simultaneously. /// - /// `unlockPointers` returns the hash map to the previous state. + /// `unlockPointers` restores the hash map to its previous state when + /// called the same number of times as `lockPointers`. pub fn lockPointers(self: *Self) void { - self.pointer_stability.lock(); + self.pointer_stability.lockShared(); } - /// Undoes a call to `lockPointers`. + /// Undoes one call to `lockPointers`. pub fn unlockPointers(self: *Self) void { - self.pointer_stability.unlock(); + self.pointer_stability.unlockShared(); } /// Clears the map but retains the backing allocation for future use. diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 58058b285af276504fe0212080fd0983c290e50f..89f1f5532387a2869fe1ed5e7c612bfe74e14ea6 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -74,17 +74,18 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// cause an existing value pointer to become invalidated will /// instead trigger an assertion. /// - /// An additional call to `lockPointers` in such state also triggers an - /// assertion. + /// `lockPointers` may be called multiple times. This allows multiple + /// independent users of the list to keep it locked simultaneously. /// - /// `unlockPointers` returns the array list to the previous state. + /// `unlockPointers` restores the list to its previous state when + /// called the same number of times as `lockPointers`. pub fn lockPointers(self: *Self) void { - self.pointer_stability.lock(); + self.pointer_stability.lockShared(); } - /// Undoes a call to `lockPointers`. + /// Undoes one call to `lockPointers`. pub fn unlockPointers(self: *Self) void { - self.pointer_stability.unlock(); + self.pointer_stability.unlockShared(); } /// ArrayList takes ownership of the passed in slice. The slice must have been @@ -698,17 +699,18 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// cause an existing value pointer to become invalidated will /// instead trigger an assertion. /// - /// An additional call to `lockPointers` in such state also triggers an - /// assertion. + /// `lockPointers` may be called multiple times. This allows multiple + /// independent users of the list to keep it locked simultaneously. /// - /// `unlockPointers` returns the unmanaged array list to the previous state. + /// `unlockPointers` restores the list to its previous state when + /// called the same number of times as `lockPointers`. pub fn lockPointers(self: *Self) void { - self.pointer_stability.lock(); + self.pointer_stability.lockShared(); } - /// Undoes a call to `lockPointers`. + /// Undoes one call to `lockPointers`. pub fn unlockPointers(self: *Self) void { - self.pointer_stability.unlock(); + self.pointer_stability.unlockShared(); } /// Convert this list into an analogous memory-managed one. diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 27d50955e8c4e966c961389951726283f927f073..7a28f5e03f595a5df83167c5e19f0318c10d893e 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1851,20 +1851,46 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize pub const SafetyLock = struct { state: State = if (runtime_safety) .unlocked else .unknown, - pub const State = if (runtime_safety) enum { unlocked, locked } else enum { unknown }; + pub const State = if (runtime_safety) enum(usize) { + unlocked = 0, + exclusive = math.maxInt(usize), + _, // shared lock count + fn isShared(state: State) bool { + return switch (state) { + _ => true, + else => false, + }; + } + } else enum { unknown }; + + /// Exclusive. Use when mutating data. pub fn lock(l: *SafetyLock) void { if (!runtime_safety) return; assert(l.state == .unlocked); - l.state = .locked; + l.state = .exclusive; } pub fn unlock(l: *SafetyLock) void { if (!runtime_safety) return; - assert(l.state == .locked); + assert(l.state == .exclusive); l.state = .unlocked; } + /// Use when consuming data in a read-only manner. + pub fn lockShared(l: *SafetyLock) void { + if (!runtime_safety) return; + assert(l.state != .exclusive); + l.state = @fromBackingInt(@backingInt(l.state) + 1); + assert(l.state.isShared()); // Catch overflow to `exclusive`. + } + + pub fn unlockShared(l: *SafetyLock) void { + if (!runtime_safety) return; + assert(l.state.isShared()); + l.state = @fromBackingInt(@backingInt(l.state) - 1); + } + pub fn assertUnlocked(l: SafetyLock) void { if (!runtime_safety) return; assert(l.state == .unlocked); @@ -1872,7 +1898,12 @@ pub const SafetyLock = struct { pub fn assertLocked(l: SafetyLock) void { if (!runtime_safety) return; - assert(l.state == .locked); + assert(l.state == .exclusive); + } + + pub fn assertLockedShared(l: SafetyLock) void { + if (!runtime_safety) return; + assert(l.state.isShared()); } }; @@ -1883,6 +1914,12 @@ test SafetyLock { safety_lock.assertLocked(); safety_lock.unlock(); safety_lock.assertUnlocked(); + safety_lock.lockShared(); + safety_lock.assertLockedShared(); + for (0..3) |_| safety_lock.lockShared(); + safety_lock.assertLockedShared(); + for (0..4) |_| safety_lock.unlockShared(); + safety_lock.assertUnlocked(); } /// Detect whether the program is being executed in the Valgrind virtual machine. diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index b11f7e4a0dee9413d8ca5a798492c48f0ba5dde1..1eafbbac83409703b0f38a2fd040840957f31e91 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -191,15 +191,16 @@ pub fn HashMap( /// cause an existing key or value pointer to become invalidated will /// instead trigger an assertion. /// - /// An additional call to `lockPointers` in such state also triggers an - /// assertion. + /// `lockPointers` may be called multiple times. This allows multiple + /// independent users of the hash map to keep it locked simultaneously. /// - /// `unlockPointers` returns the hash map to the previous state. + /// `unlockPointers` restores the hash map to its previous state when + /// called the same number of times as `lockPointers`. pub fn lockPointers(self: *Self) void { self.unmanaged.lockPointers(); } - /// Undoes a call to `lockPointers`. + /// Undoes one call to `lockPointers`. pub fn unlockPointers(self: *Self) void { self.unmanaged.unlockPointers(); } @@ -709,17 +710,18 @@ fn Custom( /// cause an existing key or value pointer to become invalidated will /// instead trigger an assertion. /// - /// An additional call to `lockPointers` in such state also triggers an - /// assertion. + /// `lockPointers` may be called multiple times. This allows multiple + /// independent users of the hash map to keep it locked simultaneously. /// - /// `unlockPointers` returns the hash map to the previous state. + /// `unlockPointers` restores the hash map to its previous state when + /// called the same number of times as `lockPointers`. pub fn lockPointers(self: *Self) void { - self.pointer_stability.lock(); + self.pointer_stability.lockShared(); } - /// Undoes a call to `lockPointers`. + /// Undoes one call to `lockPointers`. pub fn unlockPointers(self: *Self) void { - self.pointer_stability.unlock(); + self.pointer_stability.unlockShared(); } fn isUnderMaxLoadPercentage(size: Size, cap: Size) bool {