authorgravatar for jay@jayschwa.netJay Petacat <jay@jayschwa.net> 2026-09-04 22:37:27-06:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-09 02:18:52+02:00
logd7148f7d056a5891a2b4c8e22ce71ef09d60d356
tree2ed6300eb2cf2cbd7677a5f1f54c9bfffa4b9c30
parent85e44dbae2af3ce6d71210f6d91014ca09b9597a

std.debug.SafetyLock: add support for shared locking

The existing `lock` and `unlock` methods continue to act exclusively. They should be used when data may be mutated. New methods, `lockShared` and `unlockShared`, may be used for shared locking in situations where multiple independent users are reading but not mutating data.

4 files changed, 74 insertions(+), 32 deletions(-)

lib/std/array_hash_map.zig+7-6
......@@ -200,17 +200,18 @@ pub fn Custom(
200200 /// cause an existing key or value pointer to become invalidated will
201201 /// instead trigger an assertion.
202202 ///
203 /// An additional call to `lockPointers` in such state also triggers an
204 /// assertion.
203 /// `lockPointers` may be called multiple times. This allows multiple
204 /// independent users of the hash map to keep it locked simultaneously.
205205 ///
206 /// `unlockPointers` returns the hash map to the previous state.
206 /// `unlockPointers` restores the hash map to its previous state when
207 /// called the same number of times as `lockPointers`.
207208 pub fn lockPointers(self: *Self) void {
208 self.pointer_stability.lock();
209 self.pointer_stability.lockShared();
209210 }
210211
211 /// Undoes a call to `lockPointers`.
212 /// Undoes one call to `lockPointers`.
212213 pub fn unlockPointers(self: *Self) void {
213 self.pointer_stability.unlock();
214 self.pointer_stability.unlockShared();
214215 }
215216
216217 /// Clears the map but retains the backing allocation for future use.
lib/std/array_list.zig+14-12
......@@ -74,17 +74,18 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type
7474 /// cause an existing value pointer to become invalidated will
7575 /// instead trigger an assertion.
7676 ///
77 /// An additional call to `lockPointers` in such state also triggers an
78 /// assertion.
77 /// `lockPointers` may be called multiple times. This allows multiple
78 /// independent users of the list to keep it locked simultaneously.
7979 ///
80 /// `unlockPointers` returns the array list to the previous state.
80 /// `unlockPointers` restores the list to its previous state when
81 /// called the same number of times as `lockPointers`.
8182 pub fn lockPointers(self: *Self) void {
82 self.pointer_stability.lock();
83 self.pointer_stability.lockShared();
8384 }
8485
85 /// Undoes a call to `lockPointers`.
86 /// Undoes one call to `lockPointers`.
8687 pub fn unlockPointers(self: *Self) void {
87 self.pointer_stability.unlock();
88 self.pointer_stability.unlockShared();
8889 }
8990
9091 /// 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 {
698699 /// cause an existing value pointer to become invalidated will
699700 /// instead trigger an assertion.
700701 ///
701 /// An additional call to `lockPointers` in such state also triggers an
702 /// assertion.
702 /// `lockPointers` may be called multiple times. This allows multiple
703 /// independent users of the list to keep it locked simultaneously.
703704 ///
704 /// `unlockPointers` returns the unmanaged array list to the previous state.
705 /// `unlockPointers` restores the list to its previous state when
706 /// called the same number of times as `lockPointers`.
705707 pub fn lockPointers(self: *Self) void {
706 self.pointer_stability.lock();
708 self.pointer_stability.lockShared();
707709 }
708710
709 /// Undoes a call to `lockPointers`.
711 /// Undoes one call to `lockPointers`.
710712 pub fn unlockPointers(self: *Self) void {
711 self.pointer_stability.unlock();
713 self.pointer_stability.unlockShared();
712714 }
713715
714716 /// Convert this list into an analogous memory-managed one.
lib/std/debug.zig+41-4
......@@ -1851,20 +1851,46 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
18511851pub const SafetyLock = struct {
18521852 state: State = if (runtime_safety) .unlocked else .unknown,
18531853
1854 pub const State = if (runtime_safety) enum { unlocked, locked } else enum { unknown };
1854 pub const State = if (runtime_safety) enum(usize) {
1855 unlocked = 0,
1856 exclusive = math.maxInt(usize),
1857 _, // shared lock count
1858
1859 fn isShared(state: State) bool {
1860 return switch (state) {
1861 _ => true,
1862 else => false,
1863 };
1864 }
1865 } else enum { unknown };
18551866
1867 /// Exclusive. Use when mutating data.
18561868 pub fn lock(l: *SafetyLock) void {
18571869 if (!runtime_safety) return;
18581870 assert(l.state == .unlocked);
1859 l.state = .locked;
1871 l.state = .exclusive;
18601872 }
18611873
18621874 pub fn unlock(l: *SafetyLock) void {
18631875 if (!runtime_safety) return;
1864 assert(l.state == .locked);
1876 assert(l.state == .exclusive);
18651877 l.state = .unlocked;
18661878 }
18671879
1880 /// Use when consuming data in a read-only manner.
1881 pub fn lockShared(l: *SafetyLock) void {
1882 if (!runtime_safety) return;
1883 assert(l.state != .exclusive);
1884 l.state = @fromBackingInt(@backingInt(l.state) + 1);
1885 assert(l.state.isShared()); // Catch overflow to `exclusive`.
1886 }
1887
1888 pub fn unlockShared(l: *SafetyLock) void {
1889 if (!runtime_safety) return;
1890 assert(l.state.isShared());
1891 l.state = @fromBackingInt(@backingInt(l.state) - 1);
1892 }
1893
18681894 pub fn assertUnlocked(l: SafetyLock) void {
18691895 if (!runtime_safety) return;
18701896 assert(l.state == .unlocked);
......@@ -1872,7 +1898,12 @@ pub const SafetyLock = struct {
18721898
18731899 pub fn assertLocked(l: SafetyLock) void {
18741900 if (!runtime_safety) return;
1875 assert(l.state == .locked);
1901 assert(l.state == .exclusive);
1902 }
1903
1904 pub fn assertLockedShared(l: SafetyLock) void {
1905 if (!runtime_safety) return;
1906 assert(l.state.isShared());
18761907 }
18771908};
18781909
......@@ -1883,6 +1914,12 @@ test SafetyLock {
18831914 safety_lock.assertLocked();
18841915 safety_lock.unlock();
18851916 safety_lock.assertUnlocked();
1917 safety_lock.lockShared();
1918 safety_lock.assertLockedShared();
1919 for (0..3) |_| safety_lock.lockShared();
1920 safety_lock.assertLockedShared();
1921 for (0..4) |_| safety_lock.unlockShared();
1922 safety_lock.assertUnlocked();
18861923}
18871924
18881925/// Detect whether the program is being executed in the Valgrind virtual machine.
lib/std/hash_map.zig+12-10
......@@ -191,15 +191,16 @@ pub fn HashMap(
191191 /// cause an existing key or value pointer to become invalidated will
192192 /// instead trigger an assertion.
193193 ///
194 /// An additional call to `lockPointers` in such state also triggers an
195 /// assertion.
194 /// `lockPointers` may be called multiple times. This allows multiple
195 /// independent users of the hash map to keep it locked simultaneously.
196196 ///
197 /// `unlockPointers` returns the hash map to the previous state.
197 /// `unlockPointers` restores the hash map to its previous state when
198 /// called the same number of times as `lockPointers`.
198199 pub fn lockPointers(self: *Self) void {
199200 self.unmanaged.lockPointers();
200201 }
201202
202 /// Undoes a call to `lockPointers`.
203 /// Undoes one call to `lockPointers`.
203204 pub fn unlockPointers(self: *Self) void {
204205 self.unmanaged.unlockPointers();
205206 }
......@@ -709,17 +710,18 @@ fn Custom(
709710 /// cause an existing key or value pointer to become invalidated will
710711 /// instead trigger an assertion.
711712 ///
712 /// An additional call to `lockPointers` in such state also triggers an
713 /// assertion.
713 /// `lockPointers` may be called multiple times. This allows multiple
714 /// independent users of the hash map to keep it locked simultaneously.
714715 ///
715 /// `unlockPointers` returns the hash map to the previous state.
716 /// `unlockPointers` restores the hash map to its previous state when
717 /// called the same number of times as `lockPointers`.
716718 pub fn lockPointers(self: *Self) void {
717 self.pointer_stability.lock();
719 self.pointer_stability.lockShared();
718720 }
719721
720 /// Undoes a call to `lockPointers`.
722 /// Undoes one call to `lockPointers`.
721723 pub fn unlockPointers(self: *Self) void {
722 self.pointer_stability.unlock();
724 self.pointer_stability.unlockShared();
723725 }
724726
725727 fn isUnderMaxLoadPercentage(size: Size, cap: Size) bool {